Components update v.3.21.0
parent
18497d55b1
commit
defd6ff6e2
|
@ -1,6 +1,6 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import Accordion from '../accordion/Accordion.vue';
|
import Accordion from '@/components/accordion/Accordion.vue';
|
||||||
import AccordionTab from '../accordiontab/AccordionTab.vue';
|
import AccordionTab from '@/components/accordiontab/AccordionTab.vue';
|
||||||
|
|
||||||
describe('Accordion.vue', () => {
|
describe('Accordion.vue', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
|
@ -40,8 +40,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { UniqueComponentId, DomHandler } from 'primevue/utils';
|
|
||||||
import Ripple from 'primevue/ripple';
|
import Ripple from 'primevue/ripple';
|
||||||
|
import { DomHandler, UniqueComponentId } from 'primevue/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Accordion',
|
name: 'Accordion',
|
||||||
|
@ -61,11 +61,11 @@ export default {
|
||||||
},
|
},
|
||||||
expandIcon: {
|
expandIcon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'pi-chevron-right'
|
default: 'pi pi-chevron-right'
|
||||||
},
|
},
|
||||||
collapseIcon: {
|
collapseIcon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'pi-chevron-down'
|
default: 'pi pi-chevron-down'
|
||||||
},
|
},
|
||||||
tabindex: {
|
tabindex: {
|
||||||
type: Number,
|
type: Number,
|
||||||
|
@ -234,7 +234,7 @@ export default {
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
getTabHeaderIconClass(i) {
|
getTabHeaderIconClass(i) {
|
||||||
return ['p-accordion-toggle-icon pi', this.isTabActive(i) ? this.collapseIcon : this.expandIcon];
|
return ['p-accordion-toggle-icon', this.isTabActive(i) ? this.collapseIcon : this.expandIcon];
|
||||||
},
|
},
|
||||||
getTabContentClass(tab) {
|
getTabContentClass(tab) {
|
||||||
return ['p-toggleable-content', this.getTabProp(tab, 'contentClass')];
|
return ['p-toggleable-content', this.getTabProp(tab, 'contentClass')];
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import AccordionTab from '../accordiontab/AccordionTab.vue';
|
import AccordionTab from '@/components/accordiontab/AccordionTab.vue';
|
||||||
|
|
||||||
describe('AccordionTab.vue', () => {
|
describe('AccordionTab.vue', () => {
|
||||||
it('should exists', () => {
|
it('should exists', () => {
|
||||||
|
|
|
@ -76,16 +76,19 @@ export interface PrimeIconsOptions {
|
||||||
readonly ARROW_DOWN_RIGHT: string;
|
readonly ARROW_DOWN_RIGHT: string;
|
||||||
readonly ARROW_LEFT: string;
|
readonly ARROW_LEFT: string;
|
||||||
readonly ARROW_RIGHT: string;
|
readonly ARROW_RIGHT: string;
|
||||||
|
readonly ARROW_RIGHT_ARROW_LEFT: string;
|
||||||
readonly ARROW_UP: string;
|
readonly ARROW_UP: string;
|
||||||
readonly ARROW_UP_LEFT: string;
|
readonly ARROW_UP_LEFT: string;
|
||||||
readonly ARROW_UP_RIGHT: string;
|
readonly ARROW_UP_RIGHT: string;
|
||||||
readonly ARROW_H: string;
|
readonly ARROW_H: string;
|
||||||
readonly ARROW_V: string;
|
readonly ARROW_V: string;
|
||||||
|
readonly ARROW_A: string;
|
||||||
readonly AT: string;
|
readonly AT: string;
|
||||||
readonly BACKWARD: string;
|
readonly BACKWARD: string;
|
||||||
readonly BAN: string;
|
readonly BAN: string;
|
||||||
readonly BARS: string;
|
readonly BARS: string;
|
||||||
readonly BELL: string;
|
readonly BELL: string;
|
||||||
|
readonly BITCOIN: string;
|
||||||
readonly BOLT: string;
|
readonly BOLT: string;
|
||||||
readonly BOOK: string;
|
readonly BOOK: string;
|
||||||
readonly BOOKMARK: string;
|
readonly BOOKMARK: string;
|
||||||
|
@ -97,12 +100,14 @@ export interface PrimeIconsOptions {
|
||||||
readonly CALENDAR_MINUS: string;
|
readonly CALENDAR_MINUS: string;
|
||||||
readonly CALENDAR_PLUS: string;
|
readonly CALENDAR_PLUS: string;
|
||||||
readonly CALENDAR_TIMES: string;
|
readonly CALENDAR_TIMES: string;
|
||||||
|
readonly CALCULATOR: string;
|
||||||
readonly CAMERA: string;
|
readonly CAMERA: string;
|
||||||
readonly CAR: string;
|
readonly CAR: string;
|
||||||
readonly CARET_DOWN: string;
|
readonly CARET_DOWN: string;
|
||||||
readonly CARET_LEFT: string;
|
readonly CARET_LEFT: string;
|
||||||
readonly CARET_RIGHT: string;
|
readonly CARET_RIGHT: string;
|
||||||
readonly CARET_UP: string;
|
readonly CARET_UP: string;
|
||||||
|
readonly CART_PLUS: string;
|
||||||
readonly CHART_BAR: string;
|
readonly CHART_BAR: string;
|
||||||
readonly CHART_LINE: string;
|
readonly CHART_LINE: string;
|
||||||
readonly CHART_PIE: string;
|
readonly CHART_PIE: string;
|
||||||
|
@ -132,6 +137,7 @@ export interface PrimeIconsOptions {
|
||||||
readonly COPY: string;
|
readonly COPY: string;
|
||||||
readonly CREDIT_CARD: string;
|
readonly CREDIT_CARD: string;
|
||||||
readonly DATABASE: string;
|
readonly DATABASE: string;
|
||||||
|
readonly DELETELEFT: string;
|
||||||
readonly DESKTOP: string;
|
readonly DESKTOP: string;
|
||||||
readonly DIRECTIONS: string;
|
readonly DIRECTIONS: string;
|
||||||
readonly DIRECTIONS_ALT: string;
|
readonly DIRECTIONS_ALT: string;
|
||||||
|
@ -142,6 +148,7 @@ export interface PrimeIconsOptions {
|
||||||
readonly ELLIPSIS_H: string;
|
readonly ELLIPSIS_H: string;
|
||||||
readonly ELLIPSIS_V: string;
|
readonly ELLIPSIS_V: string;
|
||||||
readonly ENVELOPE: string;
|
readonly ENVELOPE: string;
|
||||||
|
readonly ERASER: string;
|
||||||
readonly EURO: string;
|
readonly EURO: string;
|
||||||
readonly EXCLAMATION_CIRCLE: string;
|
readonly EXCLAMATION_CIRCLE: string;
|
||||||
readonly EXCLAMATION_TRIANGLE: string;
|
readonly EXCLAMATION_TRIANGLE: string;
|
||||||
|
@ -152,8 +159,12 @@ export interface PrimeIconsOptions {
|
||||||
readonly FAST_BACKWARD: string;
|
readonly FAST_BACKWARD: string;
|
||||||
readonly FAST_FORWARD: string;
|
readonly FAST_FORWARD: string;
|
||||||
readonly FILE: string;
|
readonly FILE: string;
|
||||||
|
readonly FILE_EDIT: string;
|
||||||
readonly FILE_EXCEL: string;
|
readonly FILE_EXCEL: string;
|
||||||
|
readonly FILE_EXPORT: string;
|
||||||
|
readonly FILE_IMPORT: string;
|
||||||
readonly FILE_PDF: string;
|
readonly FILE_PDF: string;
|
||||||
|
readonly FILE_WORD: string;
|
||||||
readonly FILTER: string;
|
readonly FILTER: string;
|
||||||
readonly FILTER_FILL: string;
|
readonly FILTER_FILL: string;
|
||||||
readonly FILTER_SLASH: string;
|
readonly FILTER_SLASH: string;
|
||||||
|
@ -162,6 +173,7 @@ export interface PrimeIconsOptions {
|
||||||
readonly FOLDER: string;
|
readonly FOLDER: string;
|
||||||
readonly FOLDER_OPEN: string;
|
readonly FOLDER_OPEN: string;
|
||||||
readonly FORWARD: string;
|
readonly FORWARD: string;
|
||||||
|
readonly GIFT: string;
|
||||||
readonly GITHUB: string;
|
readonly GITHUB: string;
|
||||||
readonly GLOBE: string;
|
readonly GLOBE: string;
|
||||||
readonly GOOGLE: string;
|
readonly GOOGLE: string;
|
||||||
|
@ -169,6 +181,7 @@ export interface PrimeIconsOptions {
|
||||||
readonly HEART: string;
|
readonly HEART: string;
|
||||||
readonly HEART_FILL: string;
|
readonly HEART_FILL: string;
|
||||||
readonly HISTORY: string;
|
readonly HISTORY: string;
|
||||||
|
readonly HOURGLASS: string;
|
||||||
readonly HOME: string;
|
readonly HOME: string;
|
||||||
readonly ID_CARD: string;
|
readonly ID_CARD: string;
|
||||||
readonly IMAGE: string;
|
readonly IMAGE: string;
|
||||||
|
@ -178,6 +191,7 @@ export interface PrimeIconsOptions {
|
||||||
readonly INFO_CIRCLE: string;
|
readonly INFO_CIRCLE: string;
|
||||||
readonly INSTAGRAM: string;
|
readonly INSTAGRAM: string;
|
||||||
readonly KEY: string;
|
readonly KEY: string;
|
||||||
|
readonly LANGUAGE: string;
|
||||||
readonly LINK: string;
|
readonly LINK: string;
|
||||||
readonly LINKEDIN: string;
|
readonly LINKEDIN: string;
|
||||||
readonly LIST: string;
|
readonly LIST: string;
|
||||||
|
@ -185,6 +199,8 @@ export interface PrimeIconsOptions {
|
||||||
readonly LOCK_OPEN: string;
|
readonly LOCK_OPEN: string;
|
||||||
readonly MAP: string;
|
readonly MAP: string;
|
||||||
readonly MAP_MARKER: string;
|
readonly MAP_MARKER: string;
|
||||||
|
readonly MEGAPHONE: string;
|
||||||
|
readonly MICREPHONE: string;
|
||||||
readonly MICROSOFT: string;
|
readonly MICROSOFT: string;
|
||||||
readonly MINUS: string;
|
readonly MINUS: string;
|
||||||
readonly MINUS_CIRCLE: string;
|
readonly MINUS_CIRCLE: string;
|
||||||
|
@ -253,6 +269,7 @@ export interface PrimeIconsOptions {
|
||||||
readonly STEP_FORWARD: string;
|
readonly STEP_FORWARD: string;
|
||||||
readonly STEP_FORWARD_ALT: string;
|
readonly STEP_FORWARD_ALT: string;
|
||||||
readonly STOP: string;
|
readonly STOP: string;
|
||||||
|
readonly STOPWATCH: string;
|
||||||
readonly STOP_CIRCLE: string;
|
readonly STOP_CIRCLE: string;
|
||||||
readonly SUN: string;
|
readonly SUN: string;
|
||||||
readonly SYNC: string;
|
readonly SYNC: string;
|
||||||
|
@ -263,11 +280,14 @@ export interface PrimeIconsOptions {
|
||||||
readonly TELEGRAM: string;
|
readonly TELEGRAM: string;
|
||||||
readonly TH_LARGE: string;
|
readonly TH_LARGE: string;
|
||||||
readonly THUMBS_DOWN: string;
|
readonly THUMBS_DOWN: string;
|
||||||
|
readonly THUMBS_DOWN_FILL: string;
|
||||||
readonly THUMBS_UP: string;
|
readonly THUMBS_UP: string;
|
||||||
|
readonly THUMBS_UP_FILL: string;
|
||||||
readonly TICKET: string;
|
readonly TICKET: string;
|
||||||
readonly TIMES: string;
|
readonly TIMES: string;
|
||||||
readonly TIMES_CIRCLE: string;
|
readonly TIMES_CIRCLE: string;
|
||||||
readonly TRASH: string;
|
readonly TRASH: string;
|
||||||
|
readonly TRUCK: string;
|
||||||
readonly TWITTER: string;
|
readonly TWITTER: string;
|
||||||
readonly UNDO: string;
|
readonly UNDO: string;
|
||||||
readonly UNLOCK: string;
|
readonly UNLOCK: string;
|
||||||
|
@ -277,6 +297,7 @@ export interface PrimeIconsOptions {
|
||||||
readonly USER_MINUS: string;
|
readonly USER_MINUS: string;
|
||||||
readonly USER_PLUS: string;
|
readonly USER_PLUS: string;
|
||||||
readonly USERS: string;
|
readonly USERS: string;
|
||||||
|
readonly VERIFIED: string;
|
||||||
readonly VIDEO: string;
|
readonly VIDEO: string;
|
||||||
readonly VIMEO: string;
|
readonly VIMEO: string;
|
||||||
readonly VOLUME_DOWN: string;
|
readonly VOLUME_DOWN: string;
|
||||||
|
@ -287,6 +308,7 @@ export interface PrimeIconsOptions {
|
||||||
readonly WIFI: string;
|
readonly WIFI: string;
|
||||||
readonly WINDOW_MAXIMIZE: string;
|
readonly WINDOW_MAXIMIZE: string;
|
||||||
readonly WINDOW_MINIMIZE: string;
|
readonly WINDOW_MINIMIZE: string;
|
||||||
|
readonly WRENCH: string;
|
||||||
readonly YOUTUBE: string;
|
readonly YOUTUBE: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,16 +23,19 @@ const PrimeIcons = {
|
||||||
ARROW_DOWN_RIGHT: 'pi pi-arrow-down-right',
|
ARROW_DOWN_RIGHT: 'pi pi-arrow-down-right',
|
||||||
ARROW_LEFT: 'pi pi-arrow-left',
|
ARROW_LEFT: 'pi pi-arrow-left',
|
||||||
ARROW_RIGHT: 'pi pi-arrow-right',
|
ARROW_RIGHT: 'pi pi-arrow-right',
|
||||||
|
ARROW_RIGHT_ARROW_LEFT: 'pi pi-arrow-right-arrow-left',
|
||||||
ARROW_UP: 'pi pi-arrow-up',
|
ARROW_UP: 'pi pi-arrow-up',
|
||||||
ARROW_UP_LEFT: 'pi pi-arrow-up-left',
|
ARROW_UP_LEFT: 'pi pi-arrow-up-left',
|
||||||
ARROW_UP_RIGHT: 'pi pi-arrow-up-right',
|
ARROW_UP_RIGHT: 'pi pi-arrow-up-right',
|
||||||
ARROW_H: 'pi pi-arrow-h',
|
ARROW_H: 'pi pi-arrows-h',
|
||||||
ARROW_V: 'pi pi-arrow-v',
|
ARROW_V: 'pi pi-arrows-v',
|
||||||
|
ARROW_A: 'pi pi-arrows-alt',
|
||||||
AT: 'pi pi-at',
|
AT: 'pi pi-at',
|
||||||
BACKWARD: 'pi pi-backward',
|
BACKWARD: 'pi pi-backward',
|
||||||
BAN: 'pi pi-ban',
|
BAN: 'pi pi-ban',
|
||||||
BARS: 'pi pi-bars',
|
BARS: 'pi pi-bars',
|
||||||
BELL: 'pi pi-bell',
|
BELL: 'pi pi-bell',
|
||||||
|
BITCOIN: 'pi pi-bitcoin',
|
||||||
BOLT: 'pi pi-bolt',
|
BOLT: 'pi pi-bolt',
|
||||||
BOOK: 'pi pi-book',
|
BOOK: 'pi pi-book',
|
||||||
BOOKMARK: 'pi pi-bookmark',
|
BOOKMARK: 'pi pi-bookmark',
|
||||||
|
@ -44,12 +47,14 @@ const PrimeIcons = {
|
||||||
CALENDAR_MINUS: 'pi pi-calendar-minus',
|
CALENDAR_MINUS: 'pi pi-calendar-minus',
|
||||||
CALENDAR_PLUS: 'pi pi-calendar-plus',
|
CALENDAR_PLUS: 'pi pi-calendar-plus',
|
||||||
CALENDAR_TIMES: 'pi pi-calendar-times',
|
CALENDAR_TIMES: 'pi pi-calendar-times',
|
||||||
|
CALCULATOR: 'pi pi-calculator',
|
||||||
CAMERA: 'pi pi-camera',
|
CAMERA: 'pi pi-camera',
|
||||||
CAR: 'pi pi-car',
|
CAR: 'pi pi-car',
|
||||||
CARET_DOWN: 'pi pi-caret-down',
|
CARET_DOWN: 'pi pi-caret-down',
|
||||||
CARET_LEFT: 'pi pi-caret-left',
|
CARET_LEFT: 'pi pi-caret-left',
|
||||||
CARET_RIGHT: 'pi pi-caret-right',
|
CARET_RIGHT: 'pi pi-caret-right',
|
||||||
CARET_UP: 'pi pi-caret-up',
|
CARET_UP: 'pi pi-caret-up',
|
||||||
|
CART_PLUS: 'pi pi-cart-plus',
|
||||||
CHART_BAR: 'pi pi-chart-bar',
|
CHART_BAR: 'pi pi-chart-bar',
|
||||||
CHART_LINE: 'pi pi-chart-line',
|
CHART_LINE: 'pi pi-chart-line',
|
||||||
CHART_PIE: 'pi pi-chart-pie',
|
CHART_PIE: 'pi pi-chart-pie',
|
||||||
|
@ -79,6 +84,7 @@ const PrimeIcons = {
|
||||||
COPY: 'pi pi-copy',
|
COPY: 'pi pi-copy',
|
||||||
CREDIT_CARD: 'pi pi-credit-card',
|
CREDIT_CARD: 'pi pi-credit-card',
|
||||||
DATABASE: 'pi pi-database',
|
DATABASE: 'pi pi-database',
|
||||||
|
DELETELEFT: 'pi pi-delete-left',
|
||||||
DESKTOP: 'pi pi-desktop',
|
DESKTOP: 'pi pi-desktop',
|
||||||
DIRECTIONS: 'pi pi-directions',
|
DIRECTIONS: 'pi pi-directions',
|
||||||
DIRECTIONS_ALT: 'pi pi-directions-alt',
|
DIRECTIONS_ALT: 'pi pi-directions-alt',
|
||||||
|
@ -89,6 +95,7 @@ const PrimeIcons = {
|
||||||
ELLIPSIS_H: 'pi pi-ellipsis-h',
|
ELLIPSIS_H: 'pi pi-ellipsis-h',
|
||||||
ELLIPSIS_V: 'pi pi-ellipsis-v',
|
ELLIPSIS_V: 'pi pi-ellipsis-v',
|
||||||
ENVELOPE: 'pi pi-envelope',
|
ENVELOPE: 'pi pi-envelope',
|
||||||
|
ERASER: 'pi pi-eraser',
|
||||||
EURO: 'pi pi-euro',
|
EURO: 'pi pi-euro',
|
||||||
EXCLAMATION_CIRCLE: 'pi pi-exclamation-circle',
|
EXCLAMATION_CIRCLE: 'pi pi-exclamation-circle',
|
||||||
EXCLAMATION_TRIANGLE: 'pi pi-exclamation-triangle',
|
EXCLAMATION_TRIANGLE: 'pi pi-exclamation-triangle',
|
||||||
|
@ -99,8 +106,12 @@ const PrimeIcons = {
|
||||||
FAST_BACKWARD: 'pi pi-fast-backward',
|
FAST_BACKWARD: 'pi pi-fast-backward',
|
||||||
FAST_FORWARD: 'pi pi-fast-forward',
|
FAST_FORWARD: 'pi pi-fast-forward',
|
||||||
FILE: 'pi pi-file',
|
FILE: 'pi pi-file',
|
||||||
|
FILE_EDIT: 'pi pi-file-edit',
|
||||||
FILE_EXCEL: 'pi pi-file-excel',
|
FILE_EXCEL: 'pi pi-file-excel',
|
||||||
|
FILE_EXPORT: 'pi pi-file-export',
|
||||||
|
FILE_IMPORT: 'pi pi-file-import',
|
||||||
FILE_PDF: 'pi pi-file-pdf',
|
FILE_PDF: 'pi pi-file-pdf',
|
||||||
|
FILE_WORD: 'pi pi-file-word',
|
||||||
FILTER: 'pi pi-filter',
|
FILTER: 'pi pi-filter',
|
||||||
FILTER_FILL: 'pi pi-filter-fill',
|
FILTER_FILL: 'pi pi-filter-fill',
|
||||||
FILTER_SLASH: 'pi pi-filter-slash',
|
FILTER_SLASH: 'pi pi-filter-slash',
|
||||||
|
@ -109,6 +120,7 @@ const PrimeIcons = {
|
||||||
FOLDER: 'pi pi-folder',
|
FOLDER: 'pi pi-folder',
|
||||||
FOLDER_OPEN: 'pi pi-folder-open',
|
FOLDER_OPEN: 'pi pi-folder-open',
|
||||||
FORWARD: 'pi pi-forward',
|
FORWARD: 'pi pi-forward',
|
||||||
|
GIFT: 'pi pi-gift',
|
||||||
GITHUB: 'pi pi-github',
|
GITHUB: 'pi pi-github',
|
||||||
GLOBE: 'pi pi-globe',
|
GLOBE: 'pi pi-globe',
|
||||||
GOOGLE: 'pi pi-google',
|
GOOGLE: 'pi pi-google',
|
||||||
|
@ -116,6 +128,7 @@ const PrimeIcons = {
|
||||||
HEART: 'pi pi-heart',
|
HEART: 'pi pi-heart',
|
||||||
HEART_FILL: 'pi pi-heart-fill',
|
HEART_FILL: 'pi pi-heart-fill',
|
||||||
HISTORY: 'pi pi-history',
|
HISTORY: 'pi pi-history',
|
||||||
|
HOURGLASS: 'pi pi-hourglass',
|
||||||
HOME: 'pi pi-home',
|
HOME: 'pi pi-home',
|
||||||
ID_CARD: 'pi pi-id-card',
|
ID_CARD: 'pi pi-id-card',
|
||||||
IMAGE: 'pi pi-image',
|
IMAGE: 'pi pi-image',
|
||||||
|
@ -125,6 +138,7 @@ const PrimeIcons = {
|
||||||
INFO_CIRCLE: 'pi pi-info-circle',
|
INFO_CIRCLE: 'pi pi-info-circle',
|
||||||
INSTAGRAM: 'pi pi-instagram',
|
INSTAGRAM: 'pi pi-instagram',
|
||||||
KEY: 'pi pi-key',
|
KEY: 'pi pi-key',
|
||||||
|
LANGUAGE: 'pi pi-language',
|
||||||
LINK: 'pi pi-link',
|
LINK: 'pi pi-link',
|
||||||
LINKEDIN: 'pi pi-linkedin',
|
LINKEDIN: 'pi pi-linkedin',
|
||||||
LIST: 'pi pi-list',
|
LIST: 'pi pi-list',
|
||||||
|
@ -132,6 +146,8 @@ const PrimeIcons = {
|
||||||
LOCK_OPEN: 'pi pi-lock-open',
|
LOCK_OPEN: 'pi pi-lock-open',
|
||||||
MAP: 'pi pi-map',
|
MAP: 'pi pi-map',
|
||||||
MAP_MARKER: 'pi pi-map-marker',
|
MAP_MARKER: 'pi pi-map-marker',
|
||||||
|
MEGAPHONE: 'pi pi-megaphone',
|
||||||
|
MICREPHONE: 'pi pi-microphone',
|
||||||
MICROSOFT: 'pi pi-microsoft',
|
MICROSOFT: 'pi pi-microsoft',
|
||||||
MINUS: 'pi pi-minus',
|
MINUS: 'pi pi-minus',
|
||||||
MINUS_CIRCLE: 'pi pi-minus-circle',
|
MINUS_CIRCLE: 'pi pi-minus-circle',
|
||||||
|
@ -200,6 +216,7 @@ const PrimeIcons = {
|
||||||
STEP_FORWARD: 'pi pi-step-forward',
|
STEP_FORWARD: 'pi pi-step-forward',
|
||||||
STEP_FORWARD_ALT: 'pi pi-step-forward-alt',
|
STEP_FORWARD_ALT: 'pi pi-step-forward-alt',
|
||||||
STOP: 'pi pi-stop',
|
STOP: 'pi pi-stop',
|
||||||
|
STOPWATCH: 'pi pi-stop-watch',
|
||||||
STOP_CIRCLE: 'pi pi-stop-circle',
|
STOP_CIRCLE: 'pi pi-stop-circle',
|
||||||
SUN: 'pi pi-sun',
|
SUN: 'pi pi-sun',
|
||||||
SYNC: 'pi pi-sync',
|
SYNC: 'pi pi-sync',
|
||||||
|
@ -210,11 +227,14 @@ const PrimeIcons = {
|
||||||
TELEGRAM: 'pi pi-telegram',
|
TELEGRAM: 'pi pi-telegram',
|
||||||
TH_LARGE: 'pi pi-th-large',
|
TH_LARGE: 'pi pi-th-large',
|
||||||
THUMBS_DOWN: 'pi pi-thumbs-down',
|
THUMBS_DOWN: 'pi pi-thumbs-down',
|
||||||
|
THUMBS_DOWN_FILL: 'pi pi-thumbs-down-fill',
|
||||||
THUMBS_UP: 'pi pi-thumbs-up',
|
THUMBS_UP: 'pi pi-thumbs-up',
|
||||||
|
THUMBS_UP_FILL: 'pi pi-thumbs-up-fill',
|
||||||
TICKET: 'pi pi-ticket',
|
TICKET: 'pi pi-ticket',
|
||||||
TIMES: 'pi pi-times',
|
TIMES: 'pi pi-times',
|
||||||
TIMES_CIRCLE: 'pi pi-times-circle',
|
TIMES_CIRCLE: 'pi pi-times-circle',
|
||||||
TRASH: 'pi pi-trash',
|
TRASH: 'pi pi-trash',
|
||||||
|
TRUCK: 'pi pi-truck',
|
||||||
TWITTER: 'pi pi-twitter',
|
TWITTER: 'pi pi-twitter',
|
||||||
UNDO: 'pi pi-undo',
|
UNDO: 'pi pi-undo',
|
||||||
UNLOCK: 'pi pi-unlock',
|
UNLOCK: 'pi pi-unlock',
|
||||||
|
@ -224,6 +244,7 @@ const PrimeIcons = {
|
||||||
USER_MINUS: 'pi pi-user-minus',
|
USER_MINUS: 'pi pi-user-minus',
|
||||||
USER_PLUS: 'pi pi-user-plus',
|
USER_PLUS: 'pi pi-user-plus',
|
||||||
USERS: 'pi pi-users',
|
USERS: 'pi pi-users',
|
||||||
|
VERIFIED: 'pi pi-verified',
|
||||||
VIDEO: 'pi pi-video',
|
VIDEO: 'pi pi-video',
|
||||||
VIMEO: 'pi pi-vimeo',
|
VIMEO: 'pi pi-vimeo',
|
||||||
VOLUME_DOWN: 'pi pi-volume-down',
|
VOLUME_DOWN: 'pi pi-volume-down',
|
||||||
|
@ -234,6 +255,7 @@ const PrimeIcons = {
|
||||||
WIFI: 'pi pi-wifi',
|
WIFI: 'pi pi-wifi',
|
||||||
WINDOW_MAXIMIZE: 'pi pi-window-maximize',
|
WINDOW_MAXIMIZE: 'pi pi-window-maximize',
|
||||||
WINDOW_MINIMIZE: 'pi pi-window-minimize',
|
WINDOW_MINIMIZE: 'pi pi-window-minimize',
|
||||||
|
WRENCH: 'pi pi-wrench',
|
||||||
YOUTUBE: 'pi pi-youtube'
|
YOUTUBE: 'pi pi-youtube'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { HTMLAttributes, InputHTMLAttributes, VNode } from 'vue';
|
import { HTMLAttributes, InputHTMLAttributes, VNode } from 'vue';
|
||||||
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
||||||
import { VirtualScrollerProps, VirtualScrollerItemOptions } from '../virtualscroller';
|
import { VirtualScrollerItemOptions, VirtualScrollerProps } from '../virtualscroller';
|
||||||
|
|
||||||
type AutoCompleteFieldType = string | ((data: any) => string) | undefined;
|
type AutoCompleteFieldType = string | ((data: any) => string) | undefined;
|
||||||
|
|
||||||
|
@ -187,11 +187,25 @@ export interface AutoCompleteProps {
|
||||||
* Uses to pass all properties of the HTMLDivElement to the overlay panel inside the component.
|
* Uses to pass all properties of the HTMLDivElement to the overlay panel inside the component.
|
||||||
*/
|
*/
|
||||||
panelProps?: HTMLAttributes | undefined;
|
panelProps?: HTMLAttributes | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to display in the dropdown.
|
||||||
|
* Default value is 'pi pi-chevron-down'.
|
||||||
|
*/
|
||||||
|
dropdownIcon?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Style class of the dropdown button.
|
||||||
|
*/
|
||||||
|
dropdownClass?: string | undefined;
|
||||||
/**
|
/**
|
||||||
* Icon to display in loading state.
|
* Icon to display in loading state.
|
||||||
* Default value is 'pi pi-spinner pi-spin'.
|
* Default value is 'pi pi-spinner pi-spin'.
|
||||||
*/
|
*/
|
||||||
loadingIcon?: string | undefined;
|
loadingIcon?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to display in chip remove action.
|
||||||
|
* Default value is 'pi pi-times-circle'.
|
||||||
|
*/
|
||||||
|
removeTokenIcon?: string | undefined;
|
||||||
/**
|
/**
|
||||||
* Whether to use the virtualScroller feature. The properties of VirtualScroller component can be used like an object in it.
|
* Whether to use the virtualScroller feature. The properties of VirtualScroller component can be used like an object in it.
|
||||||
* @see VirtualScroller.VirtualScrollerProps
|
* @see VirtualScroller.VirtualScrollerProps
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import PrimeVue from '../config/PrimeVue';
|
import PrimeVue from 'primevue/config';
|
||||||
|
import { nextTick } from 'vue';
|
||||||
import AutoComplete from './AutoComplete.vue';
|
import AutoComplete from './AutoComplete.vue';
|
||||||
|
|
||||||
describe('AutoComplete.vue', () => {
|
describe('AutoComplete.vue', () => {
|
||||||
|
@ -37,7 +38,7 @@ describe('AutoComplete.vue', () => {
|
||||||
expect(wrapper.find('.p-autocomplete-input').exists()).toBe(true);
|
expect(wrapper.find('.p-autocomplete-input').exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('search copmlete', async () => {
|
it('search complete', async () => {
|
||||||
const event = { target: { value: 'b' } };
|
const event = { target: { value: 'b' } };
|
||||||
|
|
||||||
wrapper.vm.onInput(event);
|
wrapper.vm.onInput(event);
|
||||||
|
@ -53,4 +54,35 @@ describe('AutoComplete.vue', () => {
|
||||||
expect(wrapper.find('.p-autocomplete-items').exists()).toBe(true);
|
expect(wrapper.find('.p-autocomplete-items').exists()).toBe(true);
|
||||||
expect(wrapper.findAll('.p-autocomplete-item').length).toBe(1);
|
expect(wrapper.findAll('.p-autocomplete-item').length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('dropdown', () => {
|
||||||
|
it('should have correct custom icon', async () => {
|
||||||
|
wrapper.setProps({
|
||||||
|
dropdown: true,
|
||||||
|
dropdownIcon: 'pi pi-discord'
|
||||||
|
});
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
const token = wrapper.find('.p-button-icon');
|
||||||
|
|
||||||
|
expect(token.classes()).toContain('pi-discord');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('multiple', () => {
|
||||||
|
it('should have correct custom icon', async () => {
|
||||||
|
wrapper.setProps({
|
||||||
|
multiple: true,
|
||||||
|
removeTokenIcon: 'pi pi-discord',
|
||||||
|
modelValue: ['foo', 'bar']
|
||||||
|
});
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
wrapper.findAll('.p-autocomplete-token-icon').forEach((tokenIcon) => {
|
||||||
|
expect(tokenIcon.classes()).toContain('pi-discord');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
<slot name="chip" :value="option">
|
<slot name="chip" :value="option">
|
||||||
<span class="p-autocomplete-token-label">{{ getOptionLabel(option) }}</span>
|
<span class="p-autocomplete-token-label">{{ getOptionLabel(option) }}</span>
|
||||||
</slot>
|
</slot>
|
||||||
<span class="p-autocomplete-token-icon pi pi-times-circle" @click="removeOption($event, i)" aria-hidden="true"></span>
|
<span :class="['p-autocomplete-token-icon', removeTokenIcon]" @click="removeOption($event, i)" aria-hidden="true"></span>
|
||||||
</li>
|
</li>
|
||||||
<li class="p-autocomplete-input-token" role="option">
|
<li class="p-autocomplete-input-token" role="option">
|
||||||
<input
|
<input
|
||||||
|
@ -84,7 +84,7 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<i v-if="searching" :class="loadingIconClass" aria-hidden="true"></i>
|
<i v-if="searching" :class="loadingIconClass" aria-hidden="true"></i>
|
||||||
<Button v-if="dropdown" ref="dropdownButton" type="button" icon="pi pi-chevron-down" class="p-autocomplete-dropdown" tabindex="-1" :disabled="disabled" aria-hidden="true" @click="onDropdownClick" />
|
<Button v-if="dropdown" ref="dropdownButton" type="button" :icon="dropdownIcon" :class="['p-autocomplete-dropdown', dropdownClass]" tabindex="-1" :disabled="disabled" aria-hidden="true" @click="onDropdownClick" />
|
||||||
<span role="status" aria-live="polite" class="p-hidden-accessible">
|
<span role="status" aria-live="polite" class="p-hidden-accessible">
|
||||||
{{ searchResultMessageText }}
|
{{ searchResultMessageText }}
|
||||||
</span>
|
</span>
|
||||||
|
@ -120,15 +120,15 @@
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
<span role="status" aria-live="polite" class="p-hidden-accessible">
|
|
||||||
{{ selectedMessageText }}
|
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-if="$slots.loader" v-slot:loader="{ options }">
|
<template v-if="$slots.loader" v-slot:loader="{ options }">
|
||||||
<slot name="loader" :options="options"></slot>
|
<slot name="loader" :options="options"></slot>
|
||||||
</template>
|
</template>
|
||||||
</VirtualScroller>
|
</VirtualScroller>
|
||||||
<slot name="footer" :value="modelValue" :suggestions="visibleOptions"></slot>
|
<slot name="footer" :value="modelValue" :suggestions="visibleOptions"></slot>
|
||||||
|
<span role="status" aria-live="polite" class="p-hidden-accessible">
|
||||||
|
{{ selectedMessageText }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</Portal>
|
</Portal>
|
||||||
|
@ -136,12 +136,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ConnectedOverlayScrollHandler, UniqueComponentId, ObjectUtils, DomHandler, ZIndexUtils } from 'primevue/utils';
|
|
||||||
import OverlayEventBus from 'primevue/overlayeventbus';
|
|
||||||
import Button from 'primevue/button';
|
import Button from 'primevue/button';
|
||||||
import Ripple from 'primevue/ripple';
|
import OverlayEventBus from 'primevue/overlayeventbus';
|
||||||
import VirtualScroller from 'primevue/virtualscroller';
|
|
||||||
import Portal from 'primevue/portal';
|
import Portal from 'primevue/portal';
|
||||||
|
import Ripple from 'primevue/ripple';
|
||||||
|
import { ConnectedOverlayScrollHandler, DomHandler, ObjectUtils, UniqueComponentId, ZIndexUtils } from 'primevue/utils';
|
||||||
|
import VirtualScroller from 'primevue/virtualscroller';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AutoComplete',
|
name: 'AutoComplete',
|
||||||
|
@ -242,10 +242,22 @@ export default {
|
||||||
type: null,
|
type: null,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
dropdownIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'pi pi-chevron-down'
|
||||||
|
},
|
||||||
|
dropdownClass: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
loadingIcon: {
|
loadingIcon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'pi pi-spinner'
|
default: 'pi pi-spinner'
|
||||||
},
|
},
|
||||||
|
removeTokenIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'pi pi-times-circle'
|
||||||
|
},
|
||||||
virtualScrollerOptions: {
|
virtualScrollerOptions: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null
|
default: null
|
||||||
|
@ -398,7 +410,7 @@ export default {
|
||||||
|
|
||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
this.focused = true;
|
this.focused = true;
|
||||||
this.focusedOptionIndex = this.overlayVisible && this.autoOptionFocus ? this.findFirstFocusedOptionIndex() : -1;
|
this.focusedOptionIndex = this.focusedOptionIndex !== -1 ? this.focusedOptionIndex : this.overlayVisible && this.autoOptionFocus ? this.findFirstFocusedOptionIndex() : -1;
|
||||||
this.overlayVisible && this.scrollInView(this.focusedOptionIndex);
|
this.overlayVisible && this.scrollInView(this.focusedOptionIndex);
|
||||||
this.$emit('focus', event);
|
this.$emit('focus', event);
|
||||||
},
|
},
|
||||||
|
@ -498,7 +510,7 @@ export default {
|
||||||
let valid = false;
|
let valid = false;
|
||||||
|
|
||||||
if (this.visibleOptions) {
|
if (this.visibleOptions) {
|
||||||
const matchedValue = this.visibleOptions.find((option) => this.isOptionMatched(option, event.target.value));
|
const matchedValue = this.visibleOptions.find((option) => this.isOptionMatched(option, this.$refs.focusInput.value || ''));
|
||||||
|
|
||||||
if (matchedValue !== undefined) {
|
if (matchedValue !== undefined) {
|
||||||
valid = true;
|
valid = true;
|
||||||
|
@ -651,7 +663,15 @@ export default {
|
||||||
this.multiple && event.stopPropagation(); // To prevent onArrowRightKeyOnMultiple method
|
this.multiple && event.stopPropagation(); // To prevent onArrowRightKeyOnMultiple method
|
||||||
},
|
},
|
||||||
onHomeKey(event) {
|
onHomeKey(event) {
|
||||||
event.currentTarget.setSelectionRange(0, 0);
|
const target = event.currentTarget;
|
||||||
|
const len = target.value.length;
|
||||||
|
|
||||||
|
if (event.shiftKey) {
|
||||||
|
event.currentTarget.setSelectionRange(0, len);
|
||||||
|
} else {
|
||||||
|
event.currentTarget.setSelectionRange(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
this.focusedOptionIndex = -1;
|
this.focusedOptionIndex = -1;
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -660,7 +680,12 @@ export default {
|
||||||
const target = event.currentTarget;
|
const target = event.currentTarget;
|
||||||
const len = target.value.length;
|
const len = target.value.length;
|
||||||
|
|
||||||
target.setSelectionRange(len, len);
|
if (event.shiftKey) {
|
||||||
|
event.currentTarget.setSelectionRange(0, len);
|
||||||
|
} else {
|
||||||
|
target.setSelectionRange(len, len);
|
||||||
|
}
|
||||||
|
|
||||||
this.focusedOptionIndex = -1;
|
this.focusedOptionIndex = -1;
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
|
@ -30,6 +30,14 @@ export interface AvatarProps {
|
||||||
* Default value is 'square'.
|
* Default value is 'square'.
|
||||||
*/
|
*/
|
||||||
shape?: AvatarShapeType;
|
shape?: AvatarShapeType;
|
||||||
|
/**
|
||||||
|
* Establishes a string value that labels the component.
|
||||||
|
*/
|
||||||
|
'aria-label'?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Establishes relationships between the component and label(s) where its value should be one or more element IDs.
|
||||||
|
*/
|
||||||
|
'aria-labelledby'?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AvatarSlots {
|
export interface AvatarSlots {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="containerClass">
|
<div :class="containerClass" :aria-labelledby="ariaLabelledby" :aria-label="ariaLabel">
|
||||||
<slot>
|
<slot>
|
||||||
<span v-if="label" class="p-avatar-text">{{ label }}</span>
|
<span v-if="label" class="p-avatar-text">{{ label }}</span>
|
||||||
<span v-else-if="icon" :class="iconClass"></span>
|
<span v-else-if="icon" :class="iconClass"></span>
|
||||||
|
@ -32,6 +32,14 @@ export default {
|
||||||
shape: {
|
shape: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'square'
|
default: 'square'
|
||||||
|
},
|
||||||
|
'aria-labelledby': {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
'aria-label': {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import AvatarGroup from './AvatarGroup.vue';
|
import AvatarGroup from './AvatarGroup.vue';
|
||||||
import Avatar from '../avatar/Avatar.vue';
|
import Avatar from '@/components/avatar/Avatar.vue';
|
||||||
|
|
||||||
describe('AvatarGroup.vue', () => {
|
describe('AvatarGroup.vue', () => {
|
||||||
it('should exist', () => {
|
it('should exist', () => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import Badge from './Badge.vue';
|
import Badge from './Badge.vue';
|
||||||
import Button from '../button/Button.vue';
|
import Button from '@/components/button/Button.vue';
|
||||||
|
|
||||||
describe('Badge.vue', () => {
|
describe('Badge.vue', () => {
|
||||||
it('should exist', () => {
|
it('should exist', () => {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { config, mount } from '@vue/test-utils';
|
import { config, mount } from '@vue/test-utils';
|
||||||
|
|
||||||
import BlockUI from './BlockUI.vue';
|
import BlockUI from './BlockUI.vue';
|
||||||
import Panel from '../panel/Panel.vue';
|
import Panel from '@/components/panel/Panel.vue';
|
||||||
import Button from '../button/Button.vue';
|
import Button from '@/components/button/Button.vue';
|
||||||
|
|
||||||
config.global.mocks = {
|
config.global.mocks = {
|
||||||
$primevue: {
|
$primevue: {
|
||||||
|
@ -10,17 +9,10 @@ config.global.mocks = {
|
||||||
zIndex: {
|
zIndex: {
|
||||||
modal: 1100
|
modal: 1100
|
||||||
}
|
}
|
||||||
},
|
|
||||||
DomHandler: {
|
|
||||||
addClass: vi.fn(),
|
|
||||||
removeClass: vi.fn()
|
|
||||||
},
|
|
||||||
ZIndexUtils: {
|
|
||||||
set: vi.fn(),
|
|
||||||
clear: vi.fn()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('BlockUI.vue', () => {
|
describe('BlockUI.vue', () => {
|
||||||
it('should blocked and unblocked the panel', async () => {
|
it('should blocked and unblocked the panel', async () => {
|
||||||
const wrapper = mount({
|
const wrapper = mount({
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div ref="container" class="p-blockui-container" v-bind="$attrs">
|
<div ref="container" class="p-blockui-container" :aria-busy="isBlocked">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -29,6 +29,11 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mask: null,
|
mask: null,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isBlocked: false
|
||||||
|
};
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
blocked(newValue) {
|
blocked(newValue) {
|
||||||
if (newValue === true) this.block();
|
if (newValue === true) this.block();
|
||||||
|
@ -61,6 +66,7 @@ export default {
|
||||||
ZIndexUtils.set('modal', this.mask, this.baseZIndex + this.$primevue.config.zIndex.modal);
|
ZIndexUtils.set('modal', this.mask, this.baseZIndex + this.$primevue.config.zIndex.modal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isBlocked = true;
|
||||||
this.$emit('block');
|
this.$emit('block');
|
||||||
},
|
},
|
||||||
unblock() {
|
unblock() {
|
||||||
|
@ -79,6 +85,7 @@ export default {
|
||||||
this.$refs.container.removeChild(this.mask);
|
this.$refs.container.removeChild(this.mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isBlocked = false;
|
||||||
this.$emit('unblock');
|
this.$emit('unblock');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { VNode } from 'vue';
|
import { VNode } from 'vue';
|
||||||
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
|
||||||
import { MenuItem } from '../menuitem';
|
import { MenuItem } from '../menuitem';
|
||||||
|
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
||||||
|
|
||||||
export interface BreadcrumbProps {
|
export interface BreadcrumbProps {
|
||||||
/**
|
/**
|
||||||
|
@ -16,6 +16,14 @@ export interface BreadcrumbProps {
|
||||||
* Default value is true.
|
* Default value is true.
|
||||||
*/
|
*/
|
||||||
exact?: boolean;
|
exact?: boolean;
|
||||||
|
/**
|
||||||
|
* Defines a string value that labels an interactive element.
|
||||||
|
*/
|
||||||
|
'aria-label'?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Identifier of the underlying menu element.
|
||||||
|
*/
|
||||||
|
'aria-labelledby'?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BreadcrumbSlots {
|
export interface BreadcrumbSlots {
|
||||||
|
|
|
@ -5,7 +5,16 @@ describe('Breadcrumb', () => {
|
||||||
it('should exist', () => {
|
it('should exist', () => {
|
||||||
const wrapper = mount(Breadcrumb, {
|
const wrapper = mount(Breadcrumb, {
|
||||||
global: {
|
global: {
|
||||||
stubs: ['router-link']
|
stubs: {
|
||||||
|
'router-link': true
|
||||||
|
},
|
||||||
|
mocks: {
|
||||||
|
$router: {
|
||||||
|
currentRoute: {
|
||||||
|
path: jest.fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
home: { icon: 'pi pi-home', to: '/' },
|
home: { icon: 'pi pi-home', to: '/' },
|
||||||
|
@ -14,7 +23,7 @@ describe('Breadcrumb', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(wrapper.find('.p-breadcrumb.p-component').exists()).toBe(true);
|
expect(wrapper.find('.p-breadcrumb.p-component').exists()).toBe(true);
|
||||||
expect(wrapper.findAll('.p-breadcrumb-chevron').length).toBe(5);
|
expect(wrapper.findAll('.p-menuitem-separator').length).toBe(5);
|
||||||
expect(wrapper.findAll('.p-menuitem-text').length).toBe(5);
|
expect(wrapper.findAll('.p-menuitem-text').length).toBe(5);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<nav class="p-breadcrumb p-component" aria-label="Breadcrumb">
|
<nav class="p-breadcrumb p-component">
|
||||||
<ul>
|
<ol class="p-breadcrumb-list">
|
||||||
<BreadcrumbItem v-if="home" :item="home" class="p-breadcrumb-home" :template="$slots.item" :exact="exact" />
|
<BreadcrumbItem v-if="home" :item="home" class="p-breadcrumb-home" :template="$slots.item" :exact="exact" />
|
||||||
<template v-for="item of model" :key="item.label">
|
<template v-for="item of model" :key="item.label">
|
||||||
<li class="p-breadcrumb-chevron pi pi-chevron-right"></li>
|
<li class="p-menuitem-separator">
|
||||||
|
<span class="pi pi-chevron-right" aria-hidden="true"></span>
|
||||||
|
</li>
|
||||||
<BreadcrumbItem :item="item" :template="$slots.item" :exact="exact" />
|
<BreadcrumbItem :item="item" :template="$slots.item" :exact="exact" />
|
||||||
</template>
|
</template>
|
||||||
</ul>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -40,7 +42,7 @@ export default {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-breadcrumb ul {
|
.p-breadcrumb .p-breadcrumb-list {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
|
@ -55,6 +57,13 @@ export default {
|
||||||
|
|
||||||
.p-breadcrumb .p-menuitem-link {
|
.p-breadcrumb .p-menuitem-link {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-breadcrumb .p-menuitem-separator {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-breadcrumb::-webkit-scrollbar {
|
.p-breadcrumb::-webkit-scrollbar {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<li v-if="visible()" :class="containerClass(item)">
|
<li v-if="visible()" :class="containerClass()">
|
||||||
<template v-if="!template">
|
<template v-if="!template">
|
||||||
<router-link v-if="item.to" v-slot="{ navigate, href, isActive, isExactActive }" :to="item.to" custom>
|
<router-link v-if="item.to" v-slot="{ navigate, href, isActive, isExactActive }" :to="item.to" custom>
|
||||||
<a :href="href" :class="linkClass({ isActive, isExactActive })" @click="onClick($event, navigate)">
|
<a :href="href" :class="linkClass({ isActive, isExactActive })" :aria-current="isCurrentUrl()" @click="onClick($event, navigate)">
|
||||||
<span v-if="item.icon" :class="iconClass"></span>
|
<span v-if="item.icon" :class="iconClass"></span>
|
||||||
<span v-if="item.label" class="p-menuitem-text">{{ label() }}</span>
|
<span v-if="item.label" class="p-menuitem-text">{{ label() }}</span>
|
||||||
</a>
|
</a>
|
||||||
</router-link>
|
</router-link>
|
||||||
<a v-else :href="item.url || '#'" :class="linkClass()" @click="onClick" :target="item.target">
|
<a v-else :href="item.url || '#'" :class="linkClass()" :target="item.target" :aria-current="isCurrentUrl()" @click="onClick">
|
||||||
<span v-if="item.icon" :class="iconClass"></span>
|
<span v-if="item.icon" :class="iconClass"></span>
|
||||||
<span v-if="item.label" class="p-menuitem-text">{{ label() }}</span>
|
<span v-if="item.label" class="p-menuitem-text">{{ label() }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -37,8 +37,8 @@ export default {
|
||||||
navigate(event);
|
navigate(event);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
containerClass(item) {
|
containerClass() {
|
||||||
return [{ 'p-disabled': this.disabled(item) }, this.item.class];
|
return ['p-menuitem', { 'p-disabled': this.disabled() }, this.item.class];
|
||||||
},
|
},
|
||||||
linkClass(routerProps) {
|
linkClass(routerProps) {
|
||||||
return [
|
return [
|
||||||
|
@ -52,11 +52,17 @@ export default {
|
||||||
visible() {
|
visible() {
|
||||||
return typeof this.item.visible === 'function' ? this.item.visible() : this.item.visible !== false;
|
return typeof this.item.visible === 'function' ? this.item.visible() : this.item.visible !== false;
|
||||||
},
|
},
|
||||||
disabled(item) {
|
disabled() {
|
||||||
return typeof item.disabled === 'function' ? item.disabled() : item.disabled;
|
return typeof this.item.disabled === 'function' ? this.item.disabled() : this.item.disabled;
|
||||||
},
|
},
|
||||||
label() {
|
label() {
|
||||||
return typeof this.item.label === 'function' ? this.item.label() : this.item.label;
|
return typeof this.item.label === 'function' ? this.item.label() : this.item.label;
|
||||||
|
},
|
||||||
|
isCurrentUrl() {
|
||||||
|
const { to, url } = this.item;
|
||||||
|
let lastPath = this.$router ? this.$router.currentRoute.path : '';
|
||||||
|
|
||||||
|
return to === lastPath || url === lastPath ? 'page' : undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { h } from 'vue';
|
import Button from '@/components/button/Button.vue';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import Button from '../button/Button.vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
describe('Button.vue', () => {
|
describe('Button.vue', () => {
|
||||||
it('is Button element exist', () => {
|
it('is Button element exist', () => {
|
||||||
|
|
|
@ -16,10 +16,12 @@ export default {
|
||||||
name: 'Button',
|
name: 'Button',
|
||||||
props: {
|
props: {
|
||||||
label: {
|
label: {
|
||||||
type: String
|
type: String,
|
||||||
|
default: null
|
||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
type: String
|
type: String,
|
||||||
|
default: null
|
||||||
},
|
},
|
||||||
iconPos: {
|
iconPos: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -30,7 +32,8 @@ export default {
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
badge: {
|
badge: {
|
||||||
type: String
|
type: String,
|
||||||
|
default: null
|
||||||
},
|
},
|
||||||
badgeClass: {
|
badgeClass: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
|
@ -94,6 +94,26 @@ export interface CalendarProps {
|
||||||
* Default value is 'pi pi-calendar'.
|
* Default value is 'pi pi-calendar'.
|
||||||
*/
|
*/
|
||||||
icon?: string | undefined;
|
icon?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to show in the previous button.
|
||||||
|
* Default value is 'pi pi-chevron-left'.
|
||||||
|
*/
|
||||||
|
previousIcon?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to show in the next button.
|
||||||
|
* Default value is 'pi pi-chevron-right'.
|
||||||
|
*/
|
||||||
|
nextIcon?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to show in each of the increment buttons.
|
||||||
|
* Default value is 'pi pi-chevron-up'.
|
||||||
|
*/
|
||||||
|
incrementIcon?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to show in each of the decrement buttons.
|
||||||
|
* Default value is 'pi pi-chevron-down'.
|
||||||
|
*/
|
||||||
|
decrementIcon?: string | undefined;
|
||||||
/**
|
/**
|
||||||
* Number of months to display.
|
* Number of months to display.
|
||||||
* Default value is 1.
|
* Default value is 1.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import PrimeVue from '../config/PrimeVue';
|
import PrimeVue from 'primevue/config';
|
||||||
import Calendar from './Calendar.vue';
|
import Calendar from './Calendar.vue';
|
||||||
|
|
||||||
describe('Calendar.vue', () => {
|
describe('Calendar.vue', () => {
|
||||||
|
@ -18,6 +18,7 @@ describe('Calendar.vue', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should exist', async () => {
|
it('should exist', async () => {
|
||||||
expect(wrapper.find('.p-calendar.p-component').exists()).toBe(true);
|
expect(wrapper.find('.p-calendar.p-component').exists()).toBe(true);
|
||||||
expect(wrapper.find('.p-inputtext').exists()).toBe(true);
|
expect(wrapper.find('.p-inputtext').exists()).toBe(true);
|
||||||
|
@ -37,11 +38,12 @@ describe('Calendar.vue', () => {
|
||||||
|
|
||||||
const event = { day: 8, month: 2, year: 2022, today: false, selectable: true };
|
const event = { day: 8, month: 2, year: 2022, today: false, selectable: true };
|
||||||
|
|
||||||
const onDateSelect = vi.spyOn(wrapper.vm, 'onDateSelect');
|
const onDateSelect = jest.spyOn(wrapper.vm, 'onDateSelect');
|
||||||
|
|
||||||
await wrapper.vm.onDateSelect({ currentTarget: { focus: () => {} } }, event);
|
await wrapper.vm.onDateSelect({ currentTarget: { focus: () => {} } }, event);
|
||||||
expect(onDateSelect).toHaveBeenCalled();
|
expect(onDateSelect).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should calculate the correct view date when in range mode', async () => {
|
it('should calculate the correct view date when in range mode', async () => {
|
||||||
const dateOne = new Date();
|
const dateOne = new Date();
|
||||||
const dateTwo = new Date();
|
const dateTwo = new Date();
|
||||||
|
@ -51,4 +53,28 @@ describe('Calendar.vue', () => {
|
||||||
|
|
||||||
expect(wrapper.vm.viewDate).toEqual(dateTwo);
|
expect(wrapper.vm.viewDate).toEqual(dateTwo);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should respect custom icon settings', async () => {
|
||||||
|
await wrapper.setProps({
|
||||||
|
previousIcon: 'pi pi-discord',
|
||||||
|
nextIcon: 'pi pi-facebook',
|
||||||
|
incrementIcon: 'pi pi-linkedin',
|
||||||
|
decrementIcon: 'pi pi-microsoft',
|
||||||
|
inline: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const previousIcon = wrapper.find('.p-datepicker-prev-icon');
|
||||||
|
const nextIcon = wrapper.find('.p-datepicker-next-icon');
|
||||||
|
|
||||||
|
expect(previousIcon.classes()).toContain('pi-discord');
|
||||||
|
expect(nextIcon.classes()).toContain('pi-facebook');
|
||||||
|
|
||||||
|
await wrapper.setProps({
|
||||||
|
timeOnly: true,
|
||||||
|
hourFormat: '12'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.findAll('.pi-linkedin')).toHaveLength(3);
|
||||||
|
expect(wrapper.findAll('.pi-microsoft')).toHaveLength(3);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
:class="['p-inputtext p-component', inputClass]"
|
:class="['p-inputtext p-component', inputClass]"
|
||||||
:style="inputStyle"
|
:style="inputStyle"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
|
autocomplete="off"
|
||||||
aria-autocomplete="none"
|
aria-autocomplete="none"
|
||||||
aria-haspopup="dialog"
|
aria-haspopup="dialog"
|
||||||
:aria-expanded="overlayVisible"
|
:aria-expanded="overlayVisible"
|
||||||
|
@ -17,9 +18,10 @@
|
||||||
:aria-label="ariaLabel"
|
:aria-label="ariaLabel"
|
||||||
inputmode="none"
|
inputmode="none"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:readonly="!manualInput"
|
:readonly="!manualInput || readonly"
|
||||||
:tabindex="0"
|
:tabindex="0"
|
||||||
@input="onInput"
|
@input="onInput"
|
||||||
|
@click="onInputClick"
|
||||||
@focus="onFocus"
|
@focus="onFocus"
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
@keydown="onKeyDown"
|
@keydown="onKeyDown"
|
||||||
|
@ -68,7 +70,7 @@
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:aria-label="currentView === 'year' ? $primevue.config.locale.prevDecade : currentView === 'month' ? $primevue.config.locale.prevYear : $primevue.config.locale.prevMonth"
|
:aria-label="currentView === 'year' ? $primevue.config.locale.prevDecade : currentView === 'month' ? $primevue.config.locale.prevYear : $primevue.config.locale.prevMonth"
|
||||||
>
|
>
|
||||||
<span class="p-datepicker-prev-icon pi pi-chevron-left"></span>
|
<span :class="['p-datepicker-prev-icon', previousIcon]" />
|
||||||
</button>
|
</button>
|
||||||
<div class="p-datepicker-title">
|
<div class="p-datepicker-title">
|
||||||
<button
|
<button
|
||||||
|
@ -107,7 +109,7 @@
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:aria-label="currentView === 'year' ? $primevue.config.locale.nextDecade : currentView === 'month' ? $primevue.config.locale.nextYear : $primevue.config.locale.nextMonth"
|
:aria-label="currentView === 'year' ? $primevue.config.locale.nextDecade : currentView === 'month' ? $primevue.config.locale.nextYear : $primevue.config.locale.nextMonth"
|
||||||
>
|
>
|
||||||
<span class="p-datepicker-next-icon pi pi-chevron-right"></span>
|
<span :class="['p-datepicker-next-icon', nextIcon]" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="currentView === 'date'" class="p-datepicker-calendar-container">
|
<div v-if="currentView === 'date'" class="p-datepicker-calendar-container">
|
||||||
|
@ -184,7 +186,7 @@
|
||||||
@keyup.space="onTimePickerElementMouseUp($event)"
|
@keyup.space="onTimePickerElementMouseUp($event)"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span class="pi pi-chevron-up"></span>
|
<span :class="incrementIcon" />
|
||||||
</button>
|
</button>
|
||||||
<span>{{ formattedCurrentHour }}</span>
|
<span>{{ formattedCurrentHour }}</span>
|
||||||
<button
|
<button
|
||||||
|
@ -201,7 +203,7 @@
|
||||||
@keyup.space="onTimePickerElementMouseUp($event)"
|
@keyup.space="onTimePickerElementMouseUp($event)"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span class="pi pi-chevron-down"></span>
|
<span :class="decrementIcon" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-separator">
|
<div class="p-separator">
|
||||||
|
@ -223,7 +225,7 @@
|
||||||
@keyup.space="onTimePickerElementMouseUp($event)"
|
@keyup.space="onTimePickerElementMouseUp($event)"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span class="pi pi-chevron-up"></span>
|
<span :class="incrementIcon" />
|
||||||
</button>
|
</button>
|
||||||
<span>{{ formattedCurrentMinute }}</span>
|
<span>{{ formattedCurrentMinute }}</span>
|
||||||
<button
|
<button
|
||||||
|
@ -241,7 +243,7 @@
|
||||||
@keyup.space="onTimePickerElementMouseUp($event)"
|
@keyup.space="onTimePickerElementMouseUp($event)"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span class="pi pi-chevron-down"></span>
|
<span :class="decrementIcon" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showSeconds" class="p-separator">
|
<div v-if="showSeconds" class="p-separator">
|
||||||
|
@ -263,7 +265,7 @@
|
||||||
@keyup.space="onTimePickerElementMouseUp($event)"
|
@keyup.space="onTimePickerElementMouseUp($event)"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span class="pi pi-chevron-up"></span>
|
<span :class="incrementIcon" />
|
||||||
</button>
|
</button>
|
||||||
<span>{{ formattedCurrentSecond }}</span>
|
<span>{{ formattedCurrentSecond }}</span>
|
||||||
<button
|
<button
|
||||||
|
@ -281,7 +283,7 @@
|
||||||
@keyup.space="onTimePickerElementMouseUp($event)"
|
@keyup.space="onTimePickerElementMouseUp($event)"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span class="pi pi-chevron-down"></span>
|
<span :class="decrementIcon" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="hourFormat == '12'" class="p-separator">
|
<div v-if="hourFormat == '12'" class="p-separator">
|
||||||
|
@ -289,11 +291,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-if="hourFormat == '12'" class="p-ampm-picker">
|
<div v-if="hourFormat == '12'" class="p-ampm-picker">
|
||||||
<button v-ripple class="p-link" :aria-label="$primevue.config.locale.am" @click="toggleAMPM($event)" type="button" :disabled="disabled">
|
<button v-ripple class="p-link" :aria-label="$primevue.config.locale.am" @click="toggleAMPM($event)" type="button" :disabled="disabled">
|
||||||
<span class="pi pi-chevron-up"></span>
|
<span :class="incrementIcon" />
|
||||||
</button>
|
</button>
|
||||||
<span>{{ pm ? 'PM' : 'AM' }}</span>
|
<span>{{ pm ? $primevue.config.locale.pm : $primevue.config.locale.am }}</span>
|
||||||
<button v-ripple class="p-link" :aria-label="$primevue.config.locale.pm" @click="toggleAMPM($event)" type="button" :disabled="disabled">
|
<button v-ripple class="p-link" :aria-label="$primevue.config.locale.pm" @click="toggleAMPM($event)" type="button" :disabled="disabled">
|
||||||
<span class="pi pi-chevron-down"></span>
|
<span :class="decrementIcon" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -309,11 +311,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ConnectedOverlayScrollHandler, DomHandler, ZIndexUtils, UniqueComponentId } from 'primevue/utils';
|
|
||||||
import OverlayEventBus from 'primevue/overlayeventbus';
|
|
||||||
import Button from 'primevue/button';
|
import Button from 'primevue/button';
|
||||||
import Ripple from 'primevue/ripple';
|
import OverlayEventBus from 'primevue/overlayeventbus';
|
||||||
import Portal from 'primevue/portal';
|
import Portal from 'primevue/portal';
|
||||||
|
import Ripple from 'primevue/ripple';
|
||||||
|
import { ConnectedOverlayScrollHandler, DomHandler, UniqueComponentId, ZIndexUtils } from 'primevue/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Calendar',
|
name: 'Calendar',
|
||||||
|
@ -348,6 +350,22 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'pi pi-calendar'
|
default: 'pi pi-calendar'
|
||||||
},
|
},
|
||||||
|
previousIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'pi pi-chevron-left'
|
||||||
|
},
|
||||||
|
nextIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'pi pi-chevron-right'
|
||||||
|
},
|
||||||
|
incrementIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'pi pi-chevron-up'
|
||||||
|
},
|
||||||
|
decrementIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'pi pi-chevron-down'
|
||||||
|
},
|
||||||
numberOfMonths: {
|
numberOfMonths: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 1
|
default: 1
|
||||||
|
@ -1094,7 +1112,10 @@ export default {
|
||||||
|
|
||||||
if (this.isSingleSelection() && (!this.showTime || this.hideOnDateTimeSelect)) {
|
if (this.isSingleSelection() && (!this.showTime || this.hideOnDateTimeSelect)) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.input.focus();
|
if (this.input) {
|
||||||
|
this.input.focus();
|
||||||
|
}
|
||||||
|
|
||||||
this.overlayVisible = false;
|
this.overlayVisible = false;
|
||||||
}, 150);
|
}, 150);
|
||||||
}
|
}
|
||||||
|
@ -1347,7 +1368,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hourFormat === '12') {
|
if (this.hourFormat === '12') {
|
||||||
output += date.getHours() > 11 ? ' PM' : ' AM';
|
output += date.getHours() > 11 ? ` ${this.$primevue.config.locale.pm}` : ` ${this.$primevue.config.locale.am}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
|
@ -1598,6 +1619,10 @@ export default {
|
||||||
setTimeout(() => (this.timePickerChange = false), 0);
|
setTimeout(() => (this.timePickerChange = false), 0);
|
||||||
},
|
},
|
||||||
toggleAMPM(event) {
|
toggleAMPM(event) {
|
||||||
|
const validHour = this.validateTime(this.currentHour, this.currentMinute, this.currentSecond, !this.pm);
|
||||||
|
|
||||||
|
if (!validHour && (this.maxDate || this.minDate)) return;
|
||||||
|
|
||||||
this.pm = !this.pm;
|
this.pm = !this.pm;
|
||||||
this.updateModelTime();
|
this.updateModelTime();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -1758,7 +1783,7 @@ export default {
|
||||||
throw 'Invalid Time';
|
throw 'Invalid Time';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pm = ampm === 'PM' || ampm === 'pm';
|
this.pm = ampm === this.$primevue.config.locale.am || ampm === this.$primevue.config.locale.am.toLowerCase();
|
||||||
let time = this.parseTime(timeString);
|
let time = this.parseTime(timeString);
|
||||||
|
|
||||||
value.setHours(time.hour);
|
value.setHours(time.hour);
|
||||||
|
@ -1982,21 +2007,33 @@ export default {
|
||||||
const cellContent = event.currentTarget;
|
const cellContent = event.currentTarget;
|
||||||
const cell = cellContent.parentElement;
|
const cell = cellContent.parentElement;
|
||||||
|
|
||||||
|
const cellIndex = DomHandler.index(cell);
|
||||||
|
|
||||||
switch (event.code) {
|
switch (event.code) {
|
||||||
case 'ArrowDown': {
|
case 'ArrowDown': {
|
||||||
cellContent.tabIndex = '-1';
|
cellContent.tabIndex = '-1';
|
||||||
let cellIndex = DomHandler.index(cell);
|
|
||||||
let nextRow = cell.parentElement.nextElementSibling;
|
let nextRow = cell.parentElement.nextElementSibling;
|
||||||
|
|
||||||
if (nextRow) {
|
if (nextRow) {
|
||||||
let focusCell = nextRow.children[cellIndex].children[0];
|
let tableRowIndex = DomHandler.index(cell.parentElement);
|
||||||
|
const tableRows = Array.from(cell.parentElement.parentElement.children);
|
||||||
|
const nextTableRows = tableRows.slice(tableRowIndex + 1);
|
||||||
|
|
||||||
if (DomHandler.hasClass(focusCell, 'p-disabled')) {
|
let hasNextFocusableDate = nextTableRows.find((el) => {
|
||||||
|
let focusCell = el.children[cellIndex].children[0];
|
||||||
|
|
||||||
|
return !DomHandler.hasClass(focusCell, 'p-disabled');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasNextFocusableDate) {
|
||||||
|
let focusCell = hasNextFocusableDate.children[cellIndex].children[0];
|
||||||
|
|
||||||
|
focusCell.tabIndex = '0';
|
||||||
|
focusCell.focus();
|
||||||
|
} else {
|
||||||
this.navigationState = { backward: false };
|
this.navigationState = { backward: false };
|
||||||
this.navForward(event);
|
this.navForward(event);
|
||||||
} else {
|
|
||||||
nextRow.children[cellIndex].children[0].tabIndex = '0';
|
|
||||||
nextRow.children[cellIndex].children[0].focus();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.navigationState = { backward: false };
|
this.navigationState = { backward: false };
|
||||||
|
@ -2009,18 +2046,27 @@ export default {
|
||||||
|
|
||||||
case 'ArrowUp': {
|
case 'ArrowUp': {
|
||||||
cellContent.tabIndex = '-1';
|
cellContent.tabIndex = '-1';
|
||||||
let cellIndex = DomHandler.index(cell);
|
|
||||||
let prevRow = cell.parentElement.previousElementSibling;
|
let prevRow = cell.parentElement.previousElementSibling;
|
||||||
|
|
||||||
if (prevRow) {
|
if (prevRow) {
|
||||||
let focusCell = prevRow.children[cellIndex].children[0];
|
let tableRowIndex = DomHandler.index(cell.parentElement);
|
||||||
|
const tableRows = Array.from(cell.parentElement.parentElement.children);
|
||||||
|
const prevTableRows = tableRows.slice(0, tableRowIndex).reverse();
|
||||||
|
|
||||||
|
let hasNextFocusableDate = prevTableRows.find((el) => {
|
||||||
|
let focusCell = el.children[cellIndex].children[0];
|
||||||
|
|
||||||
|
return !DomHandler.hasClass(focusCell, 'p-disabled');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasNextFocusableDate) {
|
||||||
|
let focusCell = hasNextFocusableDate.children[cellIndex].children[0];
|
||||||
|
|
||||||
if (DomHandler.hasClass(focusCell, 'p-disabled')) {
|
|
||||||
this.navigationState = { backward: true };
|
|
||||||
this.navBackward(event);
|
|
||||||
} else {
|
|
||||||
focusCell.tabIndex = '0';
|
focusCell.tabIndex = '0';
|
||||||
focusCell.focus();
|
focusCell.focus();
|
||||||
|
} else {
|
||||||
|
this.navigationState = { backward: true };
|
||||||
|
this.navBackward(event);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.navigationState = { backward: true };
|
this.navigationState = { backward: true };
|
||||||
|
@ -2036,13 +2082,22 @@ export default {
|
||||||
let prevCell = cell.previousElementSibling;
|
let prevCell = cell.previousElementSibling;
|
||||||
|
|
||||||
if (prevCell) {
|
if (prevCell) {
|
||||||
let focusCell = prevCell.children[0];
|
const cells = Array.from(cell.parentElement.children);
|
||||||
|
const prevCells = cells.slice(0, cellIndex).reverse();
|
||||||
|
|
||||||
|
let hasNextFocusableDate = prevCells.find((el) => {
|
||||||
|
let focusCell = el.children[0];
|
||||||
|
|
||||||
|
return !DomHandler.hasClass(focusCell, 'p-disabled');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasNextFocusableDate) {
|
||||||
|
let focusCell = hasNextFocusableDate.children[0];
|
||||||
|
|
||||||
if (DomHandler.hasClass(focusCell, 'p-disabled')) {
|
|
||||||
this.navigateToMonth(event, true, groupIndex);
|
|
||||||
} else {
|
|
||||||
focusCell.tabIndex = '0';
|
focusCell.tabIndex = '0';
|
||||||
focusCell.focus();
|
focusCell.focus();
|
||||||
|
} else {
|
||||||
|
this.navigateToMonth(event, true, groupIndex);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.navigateToMonth(event, true, groupIndex);
|
this.navigateToMonth(event, true, groupIndex);
|
||||||
|
@ -2057,13 +2112,21 @@ export default {
|
||||||
let nextCell = cell.nextElementSibling;
|
let nextCell = cell.nextElementSibling;
|
||||||
|
|
||||||
if (nextCell) {
|
if (nextCell) {
|
||||||
let focusCell = nextCell.children[0];
|
const cells = Array.from(cell.parentElement.children);
|
||||||
|
const nextCells = cells.slice(cellIndex + 1);
|
||||||
|
let hasNextFocusableDate = nextCells.find((el) => {
|
||||||
|
let focusCell = el.children[0];
|
||||||
|
|
||||||
|
return !DomHandler.hasClass(focusCell, 'p-disabled');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasNextFocusableDate) {
|
||||||
|
let focusCell = hasNextFocusableDate.children[0];
|
||||||
|
|
||||||
if (DomHandler.hasClass(focusCell, 'p-disabled')) {
|
|
||||||
this.navigateToMonth(event, false, groupIndex);
|
|
||||||
} else {
|
|
||||||
focusCell.tabIndex = '0';
|
focusCell.tabIndex = '0';
|
||||||
focusCell.focus();
|
focusCell.focus();
|
||||||
|
} else {
|
||||||
|
this.navigateToMonth(event, false, groupIndex);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.navigateToMonth(event, false, groupIndex);
|
this.navigateToMonth(event, false, groupIndex);
|
||||||
|
@ -2514,6 +2577,11 @@ export default {
|
||||||
|
|
||||||
this.$emit('input', event);
|
this.$emit('input', event);
|
||||||
},
|
},
|
||||||
|
onInputClick() {
|
||||||
|
if (this.showOnFocus && this.isEnabled() && !this.overlayVisible) {
|
||||||
|
this.overlayVisible = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
onFocus(event) {
|
onFocus(event) {
|
||||||
if (this.showOnFocus && this.isEnabled()) {
|
if (this.showOnFocus && this.isEnabled()) {
|
||||||
this.overlayVisible = true;
|
this.overlayVisible = true;
|
||||||
|
@ -2635,7 +2703,7 @@ export default {
|
||||||
|
|
||||||
if (propValue && Array.isArray(propValue)) {
|
if (propValue && Array.isArray(propValue)) {
|
||||||
if (this.isRangeSelection()) {
|
if (this.isRangeSelection()) {
|
||||||
propValue = propValue[1] || propValue[0];
|
propValue = this.inline ? propValue[0] : propValue[1] || propValue[0];
|
||||||
} else if (this.isMultipleSelection()) {
|
} else if (this.isMultipleSelection()) {
|
||||||
propValue = propValue[propValue.length - 1];
|
propValue = propValue[propValue.length - 1];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { VNode } from 'vue';
|
import { ButtonHTMLAttributes, VNode } from 'vue';
|
||||||
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
||||||
|
|
||||||
type CarouselOrientationType = 'horizontal' | 'vertical' | undefined;
|
type CarouselOrientationType = 'horizontal' | 'vertical' | undefined;
|
||||||
|
@ -85,6 +85,14 @@ export interface CarouselProps {
|
||||||
* Default value is true.
|
* Default value is true.
|
||||||
*/
|
*/
|
||||||
showIndicators?: boolean | undefined;
|
showIndicators?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Uses to pass all properties of the HTMLButtonElement to the previous navigation button.
|
||||||
|
*/
|
||||||
|
prevButtonProps?: ButtonHTMLAttributes | undefined;
|
||||||
|
/**
|
||||||
|
* Uses to pass all properties of the HTMLButtonElement to the next navigation button.
|
||||||
|
*/
|
||||||
|
nextButtonProps?: ButtonHTMLAttributes | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CarouselSlots {
|
export interface CarouselSlots {
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
|
import PrimeVue from 'primevue/config';
|
||||||
import Carousel from './Carousel.vue';
|
import Carousel from './Carousel.vue';
|
||||||
|
|
||||||
describe('Carousel.vue', () => {
|
describe('Carousel.vue', () => {
|
||||||
it('should exist', async () => {
|
it('should exist', async () => {
|
||||||
const wrapper = mount(Carousel, {
|
const wrapper = mount(Carousel, {
|
||||||
|
global: {
|
||||||
|
plugins: [PrimeVue]
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,11 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<div :id="id" :class="['p-carousel p-component', { 'p-carousel-vertical': isVertical(), 'p-carousel-horizontal': !isVertical() }]">
|
<div :id="id" :class="['p-carousel p-component', { 'p-carousel-vertical': isVertical(), 'p-carousel-horizontal': !isVertical() }]" role="region">
|
||||||
<div v-if="$slots.header" class="p-carousel-header">
|
<div v-if="$slots.header" class="p-carousel-header">
|
||||||
<slot name="header"></slot>
|
<slot name="header"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div :class="contentClasses">
|
<div :class="contentClasses">
|
||||||
<div :class="containerClasses">
|
<div :class="containerClasses" :aria-live="allowAutoplay ? 'polite' : 'off'">
|
||||||
<button v-if="showNavigators" v-ripple :class="['p-carousel-prev p-link', { 'p-disabled': backwardIsDisabled }]" :disabled="backwardIsDisabled" @click="navBackward" type="button">
|
<button
|
||||||
|
v-if="showNavigators"
|
||||||
|
v-ripple
|
||||||
|
type="button"
|
||||||
|
:class="['p-carousel-prev p-link', { 'p-disabled': backwardIsDisabled }]"
|
||||||
|
:disabled="backwardIsDisabled"
|
||||||
|
:aria-label="ariaPrevButtonLabel"
|
||||||
|
@click="navBackward"
|
||||||
|
v-bind="prevButtonProps"
|
||||||
|
>
|
||||||
<span :class="['p-carousel-prev-icon pi', { 'pi-chevron-left': !isVertical(), 'pi-chevron-up': isVertical() }]"></span>
|
<span :class="['p-carousel-prev-icon pi', { 'pi-chevron-left': !isVertical(), 'pi-chevron-up': isVertical() }]"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@ -27,6 +36,10 @@
|
||||||
v-for="(item, index) of value"
|
v-for="(item, index) of value"
|
||||||
:key="index"
|
:key="index"
|
||||||
:class="['p-carousel-item', { 'p-carousel-item-active': firstIndex() <= index && lastIndex() >= index, 'p-carousel-item-start': firstIndex() === index, 'p-carousel-item-end': lastIndex() === index }]"
|
:class="['p-carousel-item', { 'p-carousel-item-active': firstIndex() <= index && lastIndex() >= index, 'p-carousel-item-start': firstIndex() === index, 'p-carousel-item-end': lastIndex() === index }]"
|
||||||
|
role="group"
|
||||||
|
:aria-hidden="firstIndex() > index || lastIndex() < index ? true : undefined"
|
||||||
|
:aria-label="ariaSlideNumber(index)"
|
||||||
|
:aria-roledescription="ariaSlideLabel"
|
||||||
>
|
>
|
||||||
<slot name="item" :data="item" :index="index"></slot>
|
<slot name="item" :data="item" :index="index"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,13 +55,22 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button v-if="showNavigators" v-ripple :class="['p-carousel-next p-link', { 'p-disabled': forwardIsDisabled }]" :disabled="forwardIsDisabled" @click="navForward" type="button">
|
<button
|
||||||
|
v-if="showNavigators"
|
||||||
|
v-ripple
|
||||||
|
type="button"
|
||||||
|
:class="['p-carousel-next p-link', { 'p-disabled': forwardIsDisabled }]"
|
||||||
|
:disabled="forwardIsDisabled"
|
||||||
|
:aria-label="ariaNextButtonLabel"
|
||||||
|
@click="navForward"
|
||||||
|
v-bind="nextButtonProps"
|
||||||
|
>
|
||||||
<span :class="['p-carousel-prev-icon pi', { 'pi-chevron-right': !isVertical(), 'pi-chevron-down': isVertical() }]"></span>
|
<span :class="['p-carousel-prev-icon pi', { 'pi-chevron-right': !isVertical(), 'pi-chevron-down': isVertical() }]"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ul v-if="totalIndicators >= 0 && showIndicators" :class="indicatorsContentClasses">
|
<ul v-if="totalIndicators >= 0 && showIndicators" ref="indicatorContent" :class="indicatorsContentClasses" @keydown="onIndicatorKeydown">
|
||||||
<li v-for="(indicator, i) of totalIndicators" :key="'p-carousel-indicator-' + i.toString()" :class="['p-carousel-indicator', { 'p-highlight': d_page === i }]">
|
<li v-for="(indicator, i) of totalIndicators" :key="'p-carousel-indicator-' + i.toString()" :class="['p-carousel-indicator', { 'p-highlight': d_page === i }]">
|
||||||
<button class="p-link" @click="onIndicatorClick($event, i)" type="button" />
|
<button class="p-link" type="button" :tabindex="d_page === i ? '0' : '-1'" :aria-label="ariaPageLabel(i + 1)" :aria-current="d_page === i ? 'page' : undefined" @click="onIndicatorClick($event, i)" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,9 +81,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { UniqueComponentId } from 'primevue/utils';
|
|
||||||
import { DomHandler } from 'primevue/utils';
|
|
||||||
import Ripple from 'primevue/ripple';
|
import Ripple from 'primevue/ripple';
|
||||||
|
import { DomHandler, UniqueComponentId } from 'primevue/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Carousel',
|
name: 'Carousel',
|
||||||
|
@ -107,8 +128,17 @@ export default {
|
||||||
showIndicators: {
|
showIndicators: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
|
},
|
||||||
|
prevButtonProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
nextButtonProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
isRemainingItemsAdded: false,
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
id: UniqueComponentId(),
|
id: UniqueComponentId(),
|
||||||
|
@ -125,7 +155,6 @@ export default {
|
||||||
swipeThreshold: 20
|
swipeThreshold: 20
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
isRemainingItemsAdded: false,
|
|
||||||
watch: {
|
watch: {
|
||||||
page(newValue) {
|
page(newValue) {
|
||||||
this.d_page = newValue;
|
this.d_page = newValue;
|
||||||
|
@ -419,6 +448,84 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onIndicatorKeydown(event) {
|
||||||
|
switch (event.code) {
|
||||||
|
case 'ArrowRight':
|
||||||
|
this.onRightKey();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ArrowLeft':
|
||||||
|
this.onLeftKey();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Home':
|
||||||
|
this.onHomeKey();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'End':
|
||||||
|
this.onEndKey();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ArrowUp':
|
||||||
|
case 'ArrowDown':
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Tab':
|
||||||
|
this.onTabKey();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRightKey() {
|
||||||
|
const indicators = [...DomHandler.find(this.$refs.indicatorContent, '.p-carousel-indicator')];
|
||||||
|
const activeIndex = this.findFocusedIndicatorIndex();
|
||||||
|
|
||||||
|
this.changedFocusedIndicator(activeIndex, activeIndex + 1 === indicators.length ? indicators.length - 1 : activeIndex + 1);
|
||||||
|
},
|
||||||
|
onLeftKey() {
|
||||||
|
const activeIndex = this.findFocusedIndicatorIndex();
|
||||||
|
|
||||||
|
this.changedFocusedIndicator(activeIndex, activeIndex - 1 <= 0 ? 0 : activeIndex - 1);
|
||||||
|
},
|
||||||
|
onHomeKey() {
|
||||||
|
const activeIndex = this.findFocusedIndicatorIndex();
|
||||||
|
|
||||||
|
this.changedFocusedIndicator(activeIndex, 0);
|
||||||
|
},
|
||||||
|
onEndKey() {
|
||||||
|
const indicators = [...DomHandler.find(this.$refs.indicatorContent, '.p-carousel-indicator')];
|
||||||
|
const activeIndex = this.findFocusedIndicatorIndex();
|
||||||
|
|
||||||
|
this.changedFocusedIndicator(activeIndex, indicators.length - 1);
|
||||||
|
},
|
||||||
|
onTabKey() {
|
||||||
|
const indicators = [...DomHandler.find(this.$refs.indicatorContent, '.p-carousel-indicator')];
|
||||||
|
const highlightedIndex = indicators.findIndex((ind) => DomHandler.hasClass(ind, 'p-highlight'));
|
||||||
|
|
||||||
|
const activeIndicator = DomHandler.findSingle(this.$refs.indicatorContent, '.p-carousel-indicator > button[tabindex="0"]');
|
||||||
|
const activeIndex = indicators.findIndex((ind) => ind === activeIndicator.parentElement);
|
||||||
|
|
||||||
|
indicators[activeIndex].children[0].tabIndex = '-1';
|
||||||
|
indicators[highlightedIndex].children[0].tabIndex = '0';
|
||||||
|
},
|
||||||
|
findFocusedIndicatorIndex() {
|
||||||
|
const indicators = [...DomHandler.find(this.$refs.indicatorContent, '.p-carousel-indicator')];
|
||||||
|
const activeIndicator = DomHandler.findSingle(this.$refs.indicatorContent, '.p-carousel-indicator > button[tabindex="0"]');
|
||||||
|
|
||||||
|
return indicators.findIndex((ind) => ind === activeIndicator.parentElement);
|
||||||
|
},
|
||||||
|
changedFocusedIndicator(prevInd, nextInd) {
|
||||||
|
const indicators = [...DomHandler.find(this.$refs.indicatorContent, '.p-carousel-indicator')];
|
||||||
|
|
||||||
|
indicators[prevInd].children[0].tabIndex = '-1';
|
||||||
|
indicators[nextInd].children[0].tabIndex = '0';
|
||||||
|
indicators[nextInd].children[0].focus();
|
||||||
|
},
|
||||||
bindDocumentListeners() {
|
bindDocumentListeners() {
|
||||||
if (!this.documentResizeListener) {
|
if (!this.documentResizeListener) {
|
||||||
this.documentResizeListener = (e) => {
|
this.documentResizeListener = (e) => {
|
||||||
|
@ -507,6 +614,12 @@ export default {
|
||||||
},
|
},
|
||||||
lastIndex() {
|
lastIndex() {
|
||||||
return this.firstIndex() + this.d_numVisible - 1;
|
return this.firstIndex() + this.d_numVisible - 1;
|
||||||
|
},
|
||||||
|
ariaSlideNumber(value) {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.slideNumber.replace(/{slideNumber}/g, value) : undefined;
|
||||||
|
},
|
||||||
|
ariaPageLabel(value) {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.pageLabel.replace(/{page}/g, value) : undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -527,6 +640,15 @@ export default {
|
||||||
},
|
},
|
||||||
indicatorsContentClasses() {
|
indicatorsContentClasses() {
|
||||||
return ['p-carousel-indicators p-reset', this.indicatorsContentClass];
|
return ['p-carousel-indicators p-reset', this.indicatorsContentClass];
|
||||||
|
},
|
||||||
|
ariaSlideLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.slide : undefined;
|
||||||
|
},
|
||||||
|
ariaPrevButtonLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.prevPageLabel : undefined;
|
||||||
|
},
|
||||||
|
ariaNextButtonLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.nextPageLabel : undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
|
|
|
@ -111,11 +111,21 @@ export interface CascadeSelectProps {
|
||||||
* Whether the dropdown is in loading state.
|
* Whether the dropdown is in loading state.
|
||||||
*/
|
*/
|
||||||
loading?: boolean | undefined;
|
loading?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to display in the dropdown.
|
||||||
|
* Default value is 'pi pi-chevron-down'.
|
||||||
|
*/
|
||||||
|
dropdownIcon?: string | undefined;
|
||||||
/**
|
/**
|
||||||
* Icon to display in loading state.
|
* Icon to display in loading state.
|
||||||
* Default value is 'pi pi-spinner pi-spin'.
|
* Default value is 'pi pi-spinner pi-spin'.
|
||||||
*/
|
*/
|
||||||
loadingIcon?: string | undefined;
|
loadingIcon?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to display in the option group.
|
||||||
|
* Default value is 'pi pi-angle-right'.
|
||||||
|
*/
|
||||||
|
optionGroupIcon?: string | undefined;
|
||||||
/**
|
/**
|
||||||
* Whether to focus on the first visible or selected element when the overlay panel is shown.
|
* Whether to focus on the first visible or selected element when the overlay panel is shown.
|
||||||
* Default value is true.
|
* Default value is true.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
import { nextTick } from 'vue';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import PrimeVue from '../config/PrimeVue';
|
import PrimeVue from 'primevue/config';
|
||||||
import CascadeSelect from './CascadeSelect.vue';
|
import CascadeSelect from './CascadeSelect.vue';
|
||||||
|
|
||||||
describe('CascadeSelect.vue', () => {
|
describe('CascadeSelect.vue', () => {
|
||||||
|
@ -96,7 +97,7 @@ describe('CascadeSelect.vue', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should exist', async () => {
|
it('should exist', () => {
|
||||||
expect(wrapper.find('.p-cascadeselect.p-component').exists()).toBe(true);
|
expect(wrapper.find('.p-cascadeselect.p-component').exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -120,4 +121,19 @@ describe('CascadeSelect.vue', () => {
|
||||||
expect(sublist.findAll('.p-cascadeselect-item.p-cascadeselect-item-group').length).toBe(2);
|
expect(sublist.findAll('.p-cascadeselect-item.p-cascadeselect-item-group').length).toBe(2);
|
||||||
expect(sublist.findAll('.p-cascadeselect-item-text')[0].text()).toBe('New South Wales');
|
expect(sublist.findAll('.p-cascadeselect-item-text')[0].text()).toBe('New South Wales');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should accept custom icons', async () => {
|
||||||
|
wrapper.setProps({
|
||||||
|
dropdownIcon: 'pi pi-discord',
|
||||||
|
optionGroupIcon: 'pi pi-bell'
|
||||||
|
});
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(wrapper.find('.p-cascadeselect-trigger-icon').classes()).toContain('pi-discord');
|
||||||
|
|
||||||
|
await wrapper.trigger('click');
|
||||||
|
|
||||||
|
expect(wrapper.find('.p-cascadeselect-group-icon').classes()).toContain('pi-bell');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -54,15 +54,16 @@
|
||||||
:optionLabel="optionLabel"
|
:optionLabel="optionLabel"
|
||||||
:optionValue="optionValue"
|
:optionValue="optionValue"
|
||||||
:optionDisabled="optionDisabled"
|
:optionDisabled="optionDisabled"
|
||||||
|
:optionGroupIcon="optionGroupIcon"
|
||||||
:optionGroupLabel="optionGroupLabel"
|
:optionGroupLabel="optionGroupLabel"
|
||||||
:optionGroupChildren="optionGroupChildren"
|
:optionGroupChildren="optionGroupChildren"
|
||||||
@option-change="onOptionChange"
|
@option-change="onOptionChange"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span role="status" aria-live="polite" class="p-hidden-accessible">
|
|
||||||
{{ selectedMessageText }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<span role="status" aria-live="polite" class="p-hidden-accessible">
|
||||||
|
{{ selectedMessageText }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</Portal>
|
</Portal>
|
||||||
|
@ -70,10 +71,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ConnectedOverlayScrollHandler, ObjectUtils, DomHandler, ZIndexUtils, UniqueComponentId } from 'primevue/utils';
|
|
||||||
import OverlayEventBus from 'primevue/overlayeventbus';
|
import OverlayEventBus from 'primevue/overlayeventbus';
|
||||||
import CascadeSelectSub from './CascadeSelectSub.vue';
|
|
||||||
import Portal from 'primevue/portal';
|
import Portal from 'primevue/portal';
|
||||||
|
import { ConnectedOverlayScrollHandler, DomHandler, ObjectUtils, UniqueComponentId, ZIndexUtils } from 'primevue/utils';
|
||||||
|
import CascadeSelectSub from './CascadeSelectSub.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CascadeSelect',
|
name: 'CascadeSelect',
|
||||||
|
@ -125,10 +126,18 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
dropdownIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'pi pi-chevron-down'
|
||||||
|
},
|
||||||
loadingIcon: {
|
loadingIcon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'pi pi-spinner pi-spin'
|
default: 'pi pi-spinner pi-spin'
|
||||||
},
|
},
|
||||||
|
optionGroupIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'pi pi-angle-right'
|
||||||
|
},
|
||||||
autoOptionFocus: {
|
autoOptionFocus: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
|
@ -765,7 +774,7 @@ export default {
|
||||||
},
|
},
|
||||||
labelClass() {
|
labelClass() {
|
||||||
return [
|
return [
|
||||||
'p-cascadeselect-label',
|
'p-cascadeselect-label p-inputtext',
|
||||||
{
|
{
|
||||||
'p-placeholder': this.label === this.placeholder,
|
'p-placeholder': this.label === this.placeholder,
|
||||||
'p-cascadeselect-label-empty': !this.$slots['value'] && (this.label === 'p-emptylabel' || this.label.length === 0)
|
'p-cascadeselect-label-empty': !this.$slots['value'] && (this.label === 'p-emptylabel' || this.label.length === 0)
|
||||||
|
@ -783,7 +792,7 @@ export default {
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
dropdownIconClass() {
|
dropdownIconClass() {
|
||||||
return ['p-cascadeselect-trigger-icon', this.loading ? this.loadingIcon : 'pi pi-chevron-down'];
|
return ['p-cascadeselect-trigger-icon', this.loading ? this.loadingIcon : this.dropdownIcon];
|
||||||
},
|
},
|
||||||
hasSelectedOption() {
|
hasSelectedOption() {
|
||||||
return ObjectUtils.isNotEmpty(this.modelValue);
|
return ObjectUtils.isNotEmpty(this.modelValue);
|
||||||
|
|
|
@ -3,27 +3,19 @@
|
||||||
<template v-for="(processedOption, index) of options" :key="getOptionLabelToRender(processedOption)">
|
<template v-for="(processedOption, index) of options" :key="getOptionLabelToRender(processedOption)">
|
||||||
<li
|
<li
|
||||||
:id="getOptionId(processedOption)"
|
:id="getOptionId(processedOption)"
|
||||||
:class="[
|
:class="getOptionClass(processedOption)"
|
||||||
'p-cascadeselect-item',
|
|
||||||
{
|
|
||||||
'p-cascadeselect-item-group': isOptionGroup(processedOption),
|
|
||||||
'p-cascadeselect-item-active p-highlight': isOptionActive(processedOption),
|
|
||||||
'p-focus': isOptionFocused(processedOption),
|
|
||||||
'p-disabled': isOptionDisabled(processedOption)
|
|
||||||
}
|
|
||||||
]"
|
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
:aria-label="getOptionLabelToRender(processedOption)"
|
:aria-label="getOptionLabelToRender(processedOption)"
|
||||||
:aria-selected="isOptionGroup(processedOption) ? undefined : isOptionSelected(processedOption)"
|
:aria-selected="isOptionGroup(processedOption) ? undefined : isOptionSelected(processedOption)"
|
||||||
:aria-expanded="isOptionGroup(processedOption) ? isOptionActive(processedOption) : undefined"
|
:aria-expanded="isOptionGroup(processedOption) ? isOptionActive(processedOption) : undefined"
|
||||||
:aria-setsize="processedOption.length"
|
|
||||||
:aria-posinset="index + 1"
|
|
||||||
:aria-level="level + 1"
|
:aria-level="level + 1"
|
||||||
|
:aria-setsize="options.length"
|
||||||
|
:aria-posinset="index + 1"
|
||||||
>
|
>
|
||||||
<div v-ripple class="p-cascadeselect-item-content" @click="onOptionClick($event, processedOption)">
|
<div v-ripple class="p-cascadeselect-item-content" @click="onOptionClick($event, processedOption)">
|
||||||
<component v-if="templates['option']" :is="templates['option']" :option="processedOption.option" />
|
<component v-if="templates['option']" :is="templates['option']" :option="processedOption.option" />
|
||||||
<span v-else class="p-cascadeselect-item-text">{{ getOptionLabelToRender(processedOption) }}</span>
|
<span v-else class="p-cascadeselect-item-text">{{ getOptionLabelToRender(processedOption) }}</span>
|
||||||
<span v-if="isOptionGroup(processedOption)" class="p-cascadeselect-group-icon pi pi-angle-right" aria-hidden="true"></span>
|
<span v-if="isOptionGroup(processedOption)" :class="['p-cascadeselect-group-icon', optionGroupIcon]" aria-hidden="true"></span>
|
||||||
</div>
|
</div>
|
||||||
<CascadeSelectSub
|
<CascadeSelectSub
|
||||||
v-if="isOptionGroup(processedOption) && isOptionActive(processedOption)"
|
v-if="isOptionGroup(processedOption) && isOptionActive(processedOption)"
|
||||||
|
@ -38,6 +30,7 @@
|
||||||
:optionLabel="optionLabel"
|
:optionLabel="optionLabel"
|
||||||
:optionValue="optionValue"
|
:optionValue="optionValue"
|
||||||
:optionDisabled="optionDisabled"
|
:optionDisabled="optionDisabled"
|
||||||
|
:optionGroupIcon="optionGroupIcon"
|
||||||
:optionGroupLabel="optionGroupLabel"
|
:optionGroupLabel="optionGroupLabel"
|
||||||
:optionGroupChildren="optionGroupChildren"
|
:optionGroupChildren="optionGroupChildren"
|
||||||
@option-change="onOptionChange"
|
@option-change="onOptionChange"
|
||||||
|
@ -48,8 +41,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ObjectUtils, DomHandler } from 'primevue/utils';
|
|
||||||
import Ripple from 'primevue/ripple';
|
import Ripple from 'primevue/ripple';
|
||||||
|
import { DomHandler, ObjectUtils } from 'primevue/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CascadeSelectSub',
|
name: 'CascadeSelectSub',
|
||||||
|
@ -61,6 +54,7 @@ export default {
|
||||||
optionLabel: String,
|
optionLabel: String,
|
||||||
optionValue: String,
|
optionValue: String,
|
||||||
optionDisabled: null,
|
optionDisabled: null,
|
||||||
|
optionGroupIcon: String,
|
||||||
optionGroupLabel: String,
|
optionGroupLabel: String,
|
||||||
optionGroupChildren: Array,
|
optionGroupChildren: Array,
|
||||||
activeOptionPath: Array,
|
activeOptionPath: Array,
|
||||||
|
@ -122,6 +116,17 @@ export default {
|
||||||
if (parseInt(containerOffset.left, 10) + itemOuterWidth + sublistWidth > viewport.width - DomHandler.calculateScrollbarWidth()) {
|
if (parseInt(containerOffset.left, 10) + itemOuterWidth + sublistWidth > viewport.width - DomHandler.calculateScrollbarWidth()) {
|
||||||
this.$el.style.left = '-100%';
|
this.$el.style.left = '-100%';
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
getOptionClass(processedOption) {
|
||||||
|
return [
|
||||||
|
'p-cascadeselect-item',
|
||||||
|
{
|
||||||
|
'p-cascadeselect-item-group': this.isOptionGroup(processedOption),
|
||||||
|
'p-cascadeselect-item-active p-highlight': this.isOptionActive(processedOption),
|
||||||
|
'p-focus': this.isOptionFocused(processedOption),
|
||||||
|
'p-disabled': this.isOptionDisabled(processedOption)
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { CanvasHTMLAttributes } from 'vue';
|
||||||
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
||||||
|
|
||||||
export interface ChartSelectEvent {
|
export interface ChartSelectEvent {
|
||||||
|
@ -42,6 +43,10 @@ export interface ChartProps {
|
||||||
* Default value is 150.
|
* Default value is 150.
|
||||||
*/
|
*/
|
||||||
height?: number | undefined;
|
height?: number | undefined;
|
||||||
|
/**
|
||||||
|
* Uses to pass all properties of the CanvasHTMLAttributes to canvas element inside the component.
|
||||||
|
*/
|
||||||
|
canvasProps?: CanvasHTMLAttributes | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChartSlots {}
|
export interface ChartSlots {}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="p-chart">
|
<div class="p-chart">
|
||||||
<canvas ref="canvas" :width="width" :height="height" @click="onCanvasClick($event)"></canvas>
|
<canvas ref="canvas" :width="width" :height="height" @click="onCanvasClick($event)" v-bind="canvasProps"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -20,6 +20,10 @@ export default {
|
||||||
height: {
|
height: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 150
|
default: 150
|
||||||
|
},
|
||||||
|
canvasProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
chart: null,
|
chart: null,
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="visible" :class="containerClass">
|
<div v-if="visible" :class="containerClass" :aria-label="label">
|
||||||
<slot>
|
<slot>
|
||||||
<img v-if="image" :src="image" />
|
<img v-if="image" :src="image" />
|
||||||
<span v-else-if="icon" :class="iconClass"></span>
|
<span v-else-if="icon" :class="iconClass"></span>
|
||||||
<div v-if="label" class="p-chip-text">{{ label }}</div>
|
<div v-if="label" class="p-chip-text">{{ label }}</div>
|
||||||
</slot>
|
</slot>
|
||||||
<span v-if="removable" tabindex="0" :class="removeIconClass" @click="close" @keydown.enter="close"></span>
|
<span v-if="removable" tabindex="0" :class="removeIconClass" @click="close" @keydown="onKeydown"></span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -41,6 +41,11 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onKeydown(event) {
|
||||||
|
if (event.key === 'Enter' || event.key === 'Backspace') {
|
||||||
|
this.close(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
close(event) {
|
close(event) {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
this.$emit('remove', event);
|
this.$emit('remove', event);
|
||||||
|
|
|
@ -36,9 +36,9 @@ export interface ChipsProps {
|
||||||
*/
|
*/
|
||||||
allowDuplicate?: boolean | undefined;
|
allowDuplicate?: boolean | undefined;
|
||||||
/**
|
/**
|
||||||
* Separator char to add an item when pressed in addition to the enter key. Currently only possible value is ','
|
* Separator char to add an item when pressed in addition to the enter key.
|
||||||
*/
|
*/
|
||||||
separator?: string | undefined;
|
separator?: string | any;
|
||||||
/**
|
/**
|
||||||
* Identifier of the focus input to match a label defined for the chips.
|
* Identifier of the focus input to match a label defined for the chips.
|
||||||
*/
|
*/
|
||||||
|
@ -55,6 +55,11 @@ export interface ChipsProps {
|
||||||
* Uses to pass all properties of the HTMLInputElement to the focusable input element inside the component.
|
* Uses to pass all properties of the HTMLInputElement to the focusable input element inside the component.
|
||||||
*/
|
*/
|
||||||
inputProps?: InputHTMLAttributes | undefined;
|
inputProps?: InputHTMLAttributes | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to display in chip remove action.
|
||||||
|
* Default value is 'pi pi-times-circle'.
|
||||||
|
*/
|
||||||
|
removeTokenIcon?: string | undefined;
|
||||||
/**
|
/**
|
||||||
* When present, it specifies that the element should be disabled.
|
* When present, it specifies that the element should be disabled.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -19,7 +19,7 @@ describe('Chips.vue', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add item', async () => {
|
it('should add item', async () => {
|
||||||
const addItem = vi.spyOn(wrapper.vm, 'addItem');
|
const addItem = jest.spyOn(wrapper.vm, 'addItem');
|
||||||
|
|
||||||
await wrapper.vm.addItem({}, 'PrimeVue', false);
|
await wrapper.vm.addItem({}, 'PrimeVue', false);
|
||||||
|
|
||||||
|
@ -30,4 +30,15 @@ describe('Chips.vue', () => {
|
||||||
expect(wrapper.find('.p-chips-token-label').exists()).toBe(true);
|
expect(wrapper.find('.p-chips-token-label').exists()).toBe(true);
|
||||||
expect(wrapper.find('.p-chips-token-label').text()).toBe('PrimeVue');
|
expect(wrapper.find('.p-chips-token-label').text()).toBe('PrimeVue');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should have correct custom chip removal icon', async () => {
|
||||||
|
await wrapper.setProps({
|
||||||
|
modelValue: ['foo', 'bar'],
|
||||||
|
removeTokenIcon: 'pi pi-discord'
|
||||||
|
});
|
||||||
|
|
||||||
|
const icon = wrapper.find('.p-chips-token-icon');
|
||||||
|
|
||||||
|
expect(icon.classes()).toContain('pi-discord');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
<slot name="chip" :value="val">
|
<slot name="chip" :value="val">
|
||||||
<span class="p-chips-token-label">{{ val }}</span>
|
<span class="p-chips-token-label">{{ val }}</span>
|
||||||
</slot>
|
</slot>
|
||||||
<span class="p-chips-token-icon pi pi-times-circle" @click="removeItem($event, i)" aria-hidden="true"></span>
|
<span :class="['p-chips-token-icon', removeTokenIcon]" @click="removeItem($event, i)" aria-hidden="true"></span>
|
||||||
</li>
|
</li>
|
||||||
<li class="p-chips-input-token" role="option">
|
<li class="p-chips-input-token" role="option">
|
||||||
<input
|
<input
|
||||||
|
@ -67,7 +67,7 @@ export default {
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
separator: {
|
separator: {
|
||||||
type: String,
|
type: [String, Object],
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
addOnBlur: {
|
addOnBlur: {
|
||||||
|
@ -102,6 +102,10 @@ export default {
|
||||||
type: null,
|
type: null,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
removeTokenIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'pi pi-times-circle'
|
||||||
|
},
|
||||||
'aria-labelledby': {
|
'aria-labelledby': {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
|
@ -175,7 +179,7 @@ export default {
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (this.separator) {
|
if (this.separator) {
|
||||||
if (this.separator === ',' && event.key === ',') {
|
if (this.separator === event.key || event.key.match(this.separator)) {
|
||||||
this.addItem(event, inputValue, true);
|
this.addItem(event, inputValue, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,6 +256,10 @@ export default {
|
||||||
this.$refs.input.value = '';
|
this.$refs.input.value = '';
|
||||||
this.inputValue = '';
|
this.inputValue = '';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.maxedOut && (this.focused = false);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
if (preventDefault) {
|
if (preventDefault) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import PrimeVue from '../config/PrimeVue';
|
import PrimeVue from 'primevue/config';
|
||||||
import ColorPicker from './ColorPicker.vue';
|
import ColorPicker from './ColorPicker.vue';
|
||||||
|
|
||||||
describe('ColorPicker.vue', () => {
|
describe('ColorPicker.vue', () => {
|
||||||
|
@ -26,7 +26,7 @@ describe('ColorPicker.vue', () => {
|
||||||
|
|
||||||
it('should input click triggered', async () => {
|
it('should input click triggered', async () => {
|
||||||
const input = wrapper.find('.p-colorpicker-preview.p-inputtext');
|
const input = wrapper.find('.p-colorpicker-preview.p-inputtext');
|
||||||
const onInputClick = vi.spyOn(wrapper.vm, 'onInputClick');
|
const onInputClick = jest.spyOn(wrapper.vm, 'onInputClick');
|
||||||
|
|
||||||
await input.trigger('click');
|
await input.trigger('click');
|
||||||
|
|
||||||
|
@ -41,8 +41,8 @@ describe('ColorPicker.vue', () => {
|
||||||
|
|
||||||
await input.trigger('click');
|
await input.trigger('click');
|
||||||
|
|
||||||
const onColorMousedown = vi.spyOn(wrapper.vm, 'onColorMousedown');
|
const onColorMousedown = jest.spyOn(wrapper.vm, 'onColorMousedown');
|
||||||
const onHueMousedown = vi.spyOn(wrapper.vm, 'onHueMousedown');
|
const onHueMousedown = jest.spyOn(wrapper.vm, 'onHueMousedown');
|
||||||
const event = { pageX: 100, pageY: 120, preventDefault: () => {} };
|
const event = { pageX: 100, pageY: 120, preventDefault: () => {} };
|
||||||
const event2 = { pageX: 70, pageY: 20, preventDefault: () => {} };
|
const event2 = { pageX: 70, pageY: 20, preventDefault: () => {} };
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ConnectedOverlayScrollHandler, DomHandler, ZIndexUtils } from 'primevue/utils';
|
|
||||||
import OverlayEventBus from 'primevue/overlayeventbus';
|
import OverlayEventBus from 'primevue/overlayeventbus';
|
||||||
import Portal from 'primevue/portal';
|
import Portal from 'primevue/portal';
|
||||||
|
import { ConnectedOverlayScrollHandler, DomHandler, ZIndexUtils } from 'primevue/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ColorPicker',
|
name: 'ColorPicker',
|
||||||
|
@ -411,16 +411,14 @@ export default {
|
||||||
this.overlayVisible = !this.overlayVisible;
|
this.overlayVisible = !this.overlayVisible;
|
||||||
},
|
},
|
||||||
onInputKeydown(event) {
|
onInputKeydown(event) {
|
||||||
switch (event.which) {
|
switch (event.code) {
|
||||||
//space
|
case 'Space':
|
||||||
case 32:
|
|
||||||
this.overlayVisible = !this.overlayVisible;
|
this.overlayVisible = !this.overlayVisible;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
//escape and tab
|
case 'Escape':
|
||||||
case 27:
|
case 'Tab':
|
||||||
case 9:
|
|
||||||
this.overlayVisible = false;
|
this.overlayVisible = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import Vue, { Plugin } from 'vue';
|
import { Plugin } from 'vue';
|
||||||
|
|
||||||
interface PrimeVueConfiguration {
|
interface PrimeVueConfiguration {
|
||||||
ripple?: boolean;
|
ripple?: boolean;
|
||||||
|
@ -26,6 +26,45 @@ interface PrimeVueLocaleAriaOptions {
|
||||||
close?: string;
|
close?: string;
|
||||||
previous?: string;
|
previous?: string;
|
||||||
next?: string;
|
next?: string;
|
||||||
|
navigation?: string;
|
||||||
|
scrollTop?: string;
|
||||||
|
moveUp?: string;
|
||||||
|
moveTop?: string;
|
||||||
|
moveDown?: string;
|
||||||
|
moveBottom?: string;
|
||||||
|
moveToTarget?: string;
|
||||||
|
moveToSource?: string;
|
||||||
|
moveAllToTarget?: string;
|
||||||
|
moveAllToSource?: string;
|
||||||
|
pageLabel?: string;
|
||||||
|
firstPageLabel?: string;
|
||||||
|
lastPageLabel?: string;
|
||||||
|
nextPageLabel?: string;
|
||||||
|
prevPageLabel?: string;
|
||||||
|
rowsPerPageLabel?: string;
|
||||||
|
previousPageLabel?: string;
|
||||||
|
jumpToPageDropdownLabel?: string;
|
||||||
|
jumpToPageInputLabel?: string;
|
||||||
|
selectRow?: string;
|
||||||
|
unselectRow?: string;
|
||||||
|
expandRow?: string;
|
||||||
|
collapseRow?: string;
|
||||||
|
showFilterMenu?: string;
|
||||||
|
hideFilterMenu?: string;
|
||||||
|
filterOperator?: string;
|
||||||
|
filterConstraint?: string;
|
||||||
|
editRow?: string;
|
||||||
|
saveEdit?: string;
|
||||||
|
cancelEdit?: string;
|
||||||
|
listView?: string;
|
||||||
|
gridView?: string;
|
||||||
|
slide?: string;
|
||||||
|
slideNumber?: string;
|
||||||
|
zoomImage?: string;
|
||||||
|
zoomIn?: string;
|
||||||
|
zoomOut?: string;
|
||||||
|
rotateRight?: string;
|
||||||
|
rotateLeft?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PrimeVueLocaleOptions {
|
interface PrimeVueLocaleOptions {
|
||||||
|
@ -55,6 +94,8 @@ interface PrimeVueLocaleOptions {
|
||||||
choose?: string;
|
choose?: string;
|
||||||
upload?: string;
|
upload?: string;
|
||||||
cancel?: string;
|
cancel?: string;
|
||||||
|
completed?: string;
|
||||||
|
pending?: string;
|
||||||
dayNames: string[];
|
dayNames: string[];
|
||||||
dayNamesShort: string[];
|
dayNamesShort: string[];
|
||||||
dayNamesMin: string[];
|
dayNamesMin: string[];
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { reactive, inject } from 'vue';
|
|
||||||
import { FilterMatchMode } from 'primevue/api';
|
import { FilterMatchMode } from 'primevue/api';
|
||||||
|
import { inject, reactive } from 'vue';
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
ripple: false,
|
ripple: false,
|
||||||
|
@ -31,6 +31,8 @@ const defaultOptions = {
|
||||||
choose: 'Choose',
|
choose: 'Choose',
|
||||||
upload: 'Upload',
|
upload: 'Upload',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
|
completed: 'Completed',
|
||||||
|
pending: 'Pending',
|
||||||
dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
||||||
dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
||||||
dayNamesMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
|
dayNamesMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
|
||||||
|
@ -77,7 +79,46 @@ const defaultOptions = {
|
||||||
unselectAll: 'All items unselected',
|
unselectAll: 'All items unselected',
|
||||||
close: 'Close',
|
close: 'Close',
|
||||||
previous: 'Previous',
|
previous: 'Previous',
|
||||||
next: 'Next'
|
next: 'Next',
|
||||||
|
navigation: 'Navigation',
|
||||||
|
scrollTop: 'Scroll Top',
|
||||||
|
moveTop: 'Move Top',
|
||||||
|
moveUp: 'Move Up',
|
||||||
|
moveDown: 'Move Down',
|
||||||
|
moveBottom: 'Move Bottom',
|
||||||
|
moveToTarget: 'Move to Target',
|
||||||
|
moveToSource: 'Move to Source',
|
||||||
|
moveAllToTarget: 'Move All to Target',
|
||||||
|
moveAllToSource: 'Move All to Source',
|
||||||
|
pageLabel: '{page}',
|
||||||
|
firstPageLabel: 'First Page',
|
||||||
|
lastPageLabel: 'Last Page',
|
||||||
|
nextPageLabel: 'Next Page',
|
||||||
|
prevPageLabel: 'Previous Page',
|
||||||
|
rowsPerPageLabel: 'Rows per page',
|
||||||
|
previousPageLabel: 'Previous Page',
|
||||||
|
jumpToPageDropdownLabel: 'Jump to Page Dropdown',
|
||||||
|
jumpToPageInputLabel: 'Jump to Page Input',
|
||||||
|
selectRow: 'Row Selected',
|
||||||
|
unselectRow: 'Row Unselected',
|
||||||
|
expandRow: 'Row Expanded',
|
||||||
|
collapseRow: 'Row Collapsed',
|
||||||
|
showFilterMenu: 'Show Filter Menu',
|
||||||
|
hideFilterMenu: 'Hide Filter Menu',
|
||||||
|
filterOperator: 'Filter Operator',
|
||||||
|
filterConstraint: 'Filter Constraint',
|
||||||
|
editRow: 'Row Edit',
|
||||||
|
saveEdit: 'Save Edit',
|
||||||
|
cancelEdit: 'Cancel Edit',
|
||||||
|
listView: 'List View',
|
||||||
|
gridView: 'Grid View',
|
||||||
|
slide: 'Slide',
|
||||||
|
slideNumber: '{slideNumber}',
|
||||||
|
zoomImage: 'Zoom Image',
|
||||||
|
zoomIn: 'Zoom In',
|
||||||
|
zoomOut: 'Zoom Out',
|
||||||
|
rotateRight: 'Rotate Right',
|
||||||
|
rotateLeft: 'Rotate Left'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
filterMatchModeOptions: {
|
filterMatchModeOptions: {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import PrimeVue from '../config/PrimeVue';
|
import PrimeVue from 'primevue/config';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import ConfirmDialog from './ConfirmDialog.vue';
|
import ConfirmDialog from './ConfirmDialog.vue';
|
||||||
|
|
||||||
|
@ -18,12 +18,13 @@ describe('ConfirmDialog', () => {
|
||||||
message: 'Are you sure you want to proceed?',
|
message: 'Are you sure you want to proceed?',
|
||||||
header: 'Confirmation',
|
header: 'Confirmation',
|
||||||
icon: 'pi pi-exclamation-triangle'
|
icon: 'pi pi-exclamation-triangle'
|
||||||
}
|
},
|
||||||
|
visible: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await wrapper.setData({ visible: true });
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
expect(wrapper.find('.p-dialog-mask .p-dialog.p-component').exists()).toBe(true);
|
expect(wrapper.find('.p-dialog-mask .p-dialog.p-component').exists()).toBe(true);
|
||||||
expect(wrapper.find('.p-dialog-title').text()).toBe('Confirmation');
|
expect(wrapper.find('.p-dialog-title').text()).toBe('Confirmation');
|
||||||
|
@ -57,15 +58,15 @@ describe('ConfirmDialog', () => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('reject');
|
console.log('reject');
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
visible: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const acceptTriggered = vi.spyOn(wrapper.componentVM.confirmation, 'accept');
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
await wrapper.setData({ visible: true });
|
|
||||||
|
|
||||||
|
const acceptTriggered = jest.spyOn(wrapper.componentVM.confirmation, 'accept');
|
||||||
const CDAcceptBtn = wrapper.find('.p-confirm-dialog-accept');
|
const CDAcceptBtn = wrapper.find('.p-confirm-dialog-accept');
|
||||||
|
|
||||||
await CDAcceptBtn.trigger('click');
|
await CDAcceptBtn.trigger('click');
|
||||||
|
@ -96,15 +97,15 @@ describe('ConfirmDialog', () => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('reject');
|
console.log('reject');
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
visible: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const rejectTriggered = vi.spyOn(wrapper.componentVM.confirmation, 'reject');
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
await wrapper.setData({ visible: true });
|
|
||||||
|
|
||||||
|
const rejectTriggered = jest.spyOn(wrapper.componentVM.confirmation, 'reject');
|
||||||
const CDRejectBtn = wrapper.find('.p-confirm-dialog-reject');
|
const CDRejectBtn = wrapper.find('.p-confirm-dialog-reject');
|
||||||
|
|
||||||
await CDRejectBtn.trigger('click');
|
await CDRejectBtn.trigger('click');
|
||||||
|
@ -127,12 +128,13 @@ describe('ConfirmDialog', () => {
|
||||||
message: 'Are you sure you want to proceed?',
|
message: 'Are you sure you want to proceed?',
|
||||||
header: 'Confirmation',
|
header: 'Confirmation',
|
||||||
icon: 'pi pi-exclamation-triangle'
|
icon: 'pi pi-exclamation-triangle'
|
||||||
}
|
},
|
||||||
|
visible: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await wrapper.setData({ visible: true });
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
const dialogCloseBtn = wrapper.find('.p-dialog-header-close');
|
const dialogCloseBtn = wrapper.find('.p-dialog-header-close');
|
||||||
|
|
||||||
|
@ -158,12 +160,13 @@ describe('ConfirmDialog', () => {
|
||||||
header: 'Delete Confirmation',
|
header: 'Delete Confirmation',
|
||||||
icon: 'pi pi-info-circle',
|
icon: 'pi pi-info-circle',
|
||||||
position: 'bottom'
|
position: 'bottom'
|
||||||
}
|
},
|
||||||
|
visible: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await wrapper.setData({ visible: true });
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
expect(wrapper.find('.p-dialog-mask.p-dialog-bottom').exists()).toBe(true);
|
expect(wrapper.find('.p-dialog-mask.p-dialog-bottom').exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<CDialog v-model:visible="visible" :modal="true" :header="header" :blockScroll="blockScroll" :position="position" class="p-confirm-dialog" :breakpoints="breakpoints" :closeOnEscape="closeOnEscape" @update:visible="onHide">
|
<CDialog v-model:visible="visible" role="alertdialog" class="p-confirm-dialog" :modal="true" :header="header" :blockScroll="blockScroll" :position="position" :breakpoints="breakpoints" :closeOnEscape="closeOnEscape" @update:visible="onHide">
|
||||||
<template v-if="!$slots.message">
|
<template v-if="!$slots.message">
|
||||||
<i :class="iconClass" />
|
<i :class="iconClass" />
|
||||||
<span class="p-confirm-dialog-message">{{ message }}</span>
|
<span class="p-confirm-dialog-message">{{ message }}</span>
|
||||||
|
@ -42,6 +42,11 @@ export default {
|
||||||
|
|
||||||
if (options.group === this.group) {
|
if (options.group === this.group) {
|
||||||
this.confirmation = options;
|
this.confirmation = options;
|
||||||
|
|
||||||
|
if (this.confirmation.onShow) {
|
||||||
|
this.confirmation.onShow();
|
||||||
|
}
|
||||||
|
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<Portal>
|
<Portal>
|
||||||
<transition name="p-confirm-popup" @enter="onEnter" @leave="onLeave" @after-leave="onAfterLeave">
|
<transition name="p-confirm-popup" @enter="onEnter" @leave="onLeave" @after-leave="onAfterLeave">
|
||||||
<div v-if="visible" :ref="containerRef" :class="containerClass" v-bind="$attrs" @click="onOverlayClick">
|
<div v-if="visible" :ref="containerRef" v-focustrap role="alertdialog" :class="containerClass" :aria-modal="visible" @click="onOverlayClick" @keydown="onOverlayKeydown" v-bind="$attrs">
|
||||||
<template v-if="!$slots.message">
|
<template v-if="!$slots.message">
|
||||||
<div class="p-confirm-popup-content">
|
<div class="p-confirm-popup-content">
|
||||||
<i :class="iconClass" />
|
<i :class="iconClass" />
|
||||||
|
@ -10,8 +10,8 @@
|
||||||
</template>
|
</template>
|
||||||
<component v-else :is="$slots.message" :message="confirmation"></component>
|
<component v-else :is="$slots.message" :message="confirmation"></component>
|
||||||
<div class="p-confirm-popup-footer">
|
<div class="p-confirm-popup-footer">
|
||||||
<CPButton :label="rejectLabel" :icon="rejectIcon" :class="rejectClass" @click="reject()" />
|
<CPButton :label="rejectLabel" :icon="rejectIcon" :class="rejectClass" @click="reject()" @keydown="onRejectKeydown" :autofocus="autoFocusReject" />
|
||||||
<CPButton :label="acceptLabel" :icon="acceptIcon" :class="acceptClass" @click="accept()" autofocus />
|
<CPButton :label="acceptLabel" :icon="acceptIcon" :class="acceptClass" @click="accept()" @keydown="onAcceptKeydown" :autofocus="autoFocusAccept" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
@ -19,11 +19,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ConfirmationEventBus from 'primevue/confirmationeventbus';
|
|
||||||
import { ConnectedOverlayScrollHandler, DomHandler, ZIndexUtils } from 'primevue/utils';
|
|
||||||
import OverlayEventBus from 'primevue/overlayeventbus';
|
|
||||||
import Button from 'primevue/button';
|
import Button from 'primevue/button';
|
||||||
|
import ConfirmationEventBus from 'primevue/confirmationeventbus';
|
||||||
|
import FocusTrap from 'primevue/focustrap';
|
||||||
|
import OverlayEventBus from 'primevue/overlayeventbus';
|
||||||
import Portal from 'primevue/portal';
|
import Portal from 'primevue/portal';
|
||||||
|
import { ConnectedOverlayScrollHandler, DomHandler, ZIndexUtils } from 'primevue/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ConfirmPopup',
|
name: 'ConfirmPopup',
|
||||||
|
@ -53,6 +54,11 @@ export default {
|
||||||
if (options.group === this.group) {
|
if (options.group === this.group) {
|
||||||
this.confirmation = options;
|
this.confirmation = options;
|
||||||
this.target = options.target;
|
this.target = options.target;
|
||||||
|
|
||||||
|
if (this.confirmation.onShow) {
|
||||||
|
this.confirmation.onShow();
|
||||||
|
}
|
||||||
|
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -101,7 +107,29 @@ export default {
|
||||||
|
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
},
|
},
|
||||||
|
onHide() {
|
||||||
|
if (this.confirmation.onHide) {
|
||||||
|
this.confirmation.onHide();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
onAcceptKeydown(event) {
|
||||||
|
if (event.code === 'Space' || event.code === 'Enter') {
|
||||||
|
this.accept();
|
||||||
|
DomHandler.focus(this.target);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRejectKeydown(event) {
|
||||||
|
if (event.code === 'Space' || event.code === 'Enter') {
|
||||||
|
this.reject();
|
||||||
|
DomHandler.focus(this.target);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
onEnter(el) {
|
onEnter(el) {
|
||||||
|
this.focus();
|
||||||
this.bindOutsideClickListener();
|
this.bindOutsideClickListener();
|
||||||
this.bindScrollListener();
|
this.bindScrollListener();
|
||||||
this.bindResizeListener();
|
this.bindResizeListener();
|
||||||
|
@ -137,6 +165,10 @@ export default {
|
||||||
if (!this.outsideClickListener) {
|
if (!this.outsideClickListener) {
|
||||||
this.outsideClickListener = (event) => {
|
this.outsideClickListener = (event) => {
|
||||||
if (this.visible && this.container && !this.container.contains(event.target) && !this.isTargetClicked(event)) {
|
if (this.visible && this.container && !this.container.contains(event.target) && !this.isTargetClicked(event)) {
|
||||||
|
if (this.confirmation.onHide) {
|
||||||
|
this.confirmation.onHide();
|
||||||
|
}
|
||||||
|
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
} else {
|
} else {
|
||||||
this.alignOverlay();
|
this.alignOverlay();
|
||||||
|
@ -185,6 +217,13 @@ export default {
|
||||||
this.resizeListener = null;
|
this.resizeListener = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
focus() {
|
||||||
|
let focusTarget = this.container.querySelector('[autofocus]');
|
||||||
|
|
||||||
|
if (focusTarget) {
|
||||||
|
focusTarget.focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
isTargetClicked(event) {
|
isTargetClicked(event) {
|
||||||
return this.target && (this.target === event.target || this.target.contains(event.target));
|
return this.target && (this.target === event.target || this.target.contains(event.target));
|
||||||
},
|
},
|
||||||
|
@ -196,6 +235,12 @@ export default {
|
||||||
originalEvent: event,
|
originalEvent: event,
|
||||||
target: this.target
|
target: this.target
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
onOverlayKeydown(event) {
|
||||||
|
if (event.code === 'Escape') {
|
||||||
|
ConfirmationEventBus.emit('close', this.closeListener);
|
||||||
|
DomHandler.focus(this.target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -231,11 +276,20 @@ export default {
|
||||||
},
|
},
|
||||||
rejectClass() {
|
rejectClass() {
|
||||||
return ['p-confirm-popup-reject p-button-sm', this.confirmation ? this.confirmation.rejectClass || 'p-button-text' : null];
|
return ['p-confirm-popup-reject p-button-sm', this.confirmation ? this.confirmation.rejectClass || 'p-button-text' : null];
|
||||||
|
},
|
||||||
|
autoFocusAccept() {
|
||||||
|
return this.confirmation.defaultFocus === undefined || this.confirmation.defaultFocus === 'accept' ? true : false;
|
||||||
|
},
|
||||||
|
autoFocusReject() {
|
||||||
|
return this.confirmation.defaultFocus === 'reject' ? true : false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
CPButton: Button,
|
CPButton: Button,
|
||||||
Portal: Portal
|
Portal: Portal
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
focustrap: FocusTrap
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { VNode } from 'vue';
|
import { VNode } from 'vue';
|
||||||
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
|
||||||
import { MenuItem } from '../menuitem';
|
import { MenuItem } from '../menuitem';
|
||||||
|
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
||||||
|
|
||||||
type ContextMenuAppendTo = 'body' | 'self' | string | undefined | HTMLElement;
|
type ContextMenuAppendTo = 'body' | 'self' | string | undefined | HTMLElement;
|
||||||
|
|
||||||
|
@ -34,6 +34,18 @@ export interface ContextMenuProps {
|
||||||
* Default value is true.
|
* Default value is true.
|
||||||
*/
|
*/
|
||||||
exact?: boolean | undefined;
|
exact?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Index of the element in tabbing order.
|
||||||
|
*/
|
||||||
|
tabindex?: number | string | undefined;
|
||||||
|
/**
|
||||||
|
* Defines a string value that labels an interactive element.
|
||||||
|
*/
|
||||||
|
'aria-label'?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Identifier of the underlying menu element.
|
||||||
|
*/
|
||||||
|
'aria-labelledby'?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContextMenuSlots {
|
export interface ContextMenuSlots {
|
||||||
|
@ -49,7 +61,34 @@ export interface ContextMenuSlots {
|
||||||
}) => VNode[];
|
}) => VNode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare type ContextMenuEmits = {};
|
export declare type ContextMenuEmits = {
|
||||||
|
/**
|
||||||
|
* Callback to invoke when the component receives focus.
|
||||||
|
* @param {Event} event - Browser event.
|
||||||
|
*/
|
||||||
|
focus: (event: Event) => void;
|
||||||
|
/**
|
||||||
|
* Callback to invoke when the component loses focus.
|
||||||
|
* @param {Event} event - Browser event.
|
||||||
|
*/
|
||||||
|
blur: (event: Event) => void;
|
||||||
|
/**
|
||||||
|
* Callback to invoke before the popup is shown.
|
||||||
|
*/
|
||||||
|
'before-show': () => void;
|
||||||
|
/**
|
||||||
|
* Callback to invoke before the popup is hidden.
|
||||||
|
*/
|
||||||
|
'before-hide': () => void;
|
||||||
|
/**
|
||||||
|
* Callback to invoke when the popup is shown.
|
||||||
|
*/
|
||||||
|
show: () => void;
|
||||||
|
/**
|
||||||
|
* Callback to invoke when the popup is hidden.
|
||||||
|
*/
|
||||||
|
hide: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
declare class ContextMenu extends ClassComponent<ContextMenuProps, ContextMenuSlots, ContextMenuEmits> {
|
declare class ContextMenu extends ClassComponent<ContextMenuProps, ContextMenuSlots, ContextMenuEmits> {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import PrimeVue from '../config/PrimeVue';
|
import PrimeVue from 'primevue/config';
|
||||||
import ContextMenu from './ContextMenu.vue';
|
import ContextMenu from './ContextMenu.vue';
|
||||||
|
|
||||||
describe('ContextMenu.vue', () => {
|
describe('ContextMenu.vue', () => {
|
||||||
|
@ -147,7 +147,7 @@ describe('ContextMenu.vue', () => {
|
||||||
|
|
||||||
it('should exist', async () => {
|
it('should exist', async () => {
|
||||||
const event = { pageX: 100, pageY: 120, preventDefault: () => {}, stopPropagation: () => {} };
|
const event = { pageX: 100, pageY: 120, preventDefault: () => {}, stopPropagation: () => {} };
|
||||||
const show = vi.spyOn(wrapper.vm, 'show');
|
const show = jest.spyOn(wrapper.vm, 'show');
|
||||||
|
|
||||||
wrapper.vm.show(event);
|
wrapper.vm.show(event);
|
||||||
await wrapper.setData({ visible: true });
|
await wrapper.setData({ visible: true });
|
||||||
|
@ -159,7 +159,7 @@ describe('ContextMenu.vue', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should hide menu', async () => {
|
it('should hide menu', async () => {
|
||||||
const hide = vi.spyOn(wrapper.vm, 'hide');
|
const hide = jest.spyOn(wrapper.vm, 'hide');
|
||||||
|
|
||||||
await wrapper.setData({ visible: true });
|
await wrapper.setData({ visible: true });
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,46 @@
|
||||||
<template>
|
<template>
|
||||||
<Portal :appendTo="appendTo">
|
<Portal :appendTo="appendTo">
|
||||||
<transition name="p-contextmenu" @enter="onEnter" @leave="onLeave" @after-leave="onAfterLeave">
|
<transition name="p-contextmenu" @enter="onEnter" @after-enter="onAfterEnter" @leave="onLeave" @after-leave="onAfterLeave">
|
||||||
<div v-if="visible" :ref="containerRef" :class="containerClass" v-bind="$attrs">
|
<div v-if="visible" :ref="containerRef" :class="containerClass" v-bind="$attrs">
|
||||||
<ContextMenuSub :model="model" :root="true" @leaf-click="onLeafClick" :template="$slots.item" :exact="exact" />
|
<ContextMenuSub
|
||||||
|
:ref="listRef"
|
||||||
|
:id="id + '_list'"
|
||||||
|
class="p-contextmenu-root-list"
|
||||||
|
role="menubar"
|
||||||
|
:root="true"
|
||||||
|
:tabindex="tabindex"
|
||||||
|
aria-orientation="vertical"
|
||||||
|
:aria-activedescendant="focused ? focusedItemId : undefined"
|
||||||
|
:menuId="id"
|
||||||
|
:focusedItemId="focused ? focusedItemId : undefined"
|
||||||
|
:items="processedItems"
|
||||||
|
:template="$slots.item"
|
||||||
|
:activeItemPath="activeItemPath"
|
||||||
|
:exact="exact"
|
||||||
|
:aria-labelledby="ariaLabelledby"
|
||||||
|
:aria-label="ariaLabel"
|
||||||
|
:level="0"
|
||||||
|
:visible="submenuVisible"
|
||||||
|
@focus="onFocus"
|
||||||
|
@blur="onBlur"
|
||||||
|
@keydown="onKeyDown"
|
||||||
|
@item-click="onItemClick"
|
||||||
|
@item-mouseenter="onItemMouseEnter"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</Portal>
|
</Portal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { DomHandler, ZIndexUtils } from 'primevue/utils';
|
|
||||||
import ContextMenuSub from './ContextMenuSub.vue';
|
|
||||||
import Portal from 'primevue/portal';
|
import Portal from 'primevue/portal';
|
||||||
|
import { DomHandler, ObjectUtils, UniqueComponentId, ZIndexUtils } from 'primevue/utils';
|
||||||
|
import ContextMenuSub from './ContextMenuSub.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ContextMenu',
|
name: 'ContextMenu',
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
|
emits: ['focus', 'blur', 'show', 'hide'],
|
||||||
props: {
|
props: {
|
||||||
model: {
|
model: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
@ -40,6 +65,18 @@ export default {
|
||||||
exact: {
|
exact: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
|
},
|
||||||
|
tabindex: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
'aria-labelledby': {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
'aria-label': {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
target: null,
|
target: null,
|
||||||
|
@ -49,11 +86,29 @@ export default {
|
||||||
pageX: null,
|
pageX: null,
|
||||||
pageY: null,
|
pageY: null,
|
||||||
container: null,
|
container: null,
|
||||||
|
list: null,
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
visible: false
|
focused: false,
|
||||||
|
focusedItemInfo: { index: -1, level: 0, parentKey: '' },
|
||||||
|
activeItemPath: [],
|
||||||
|
visible: false,
|
||||||
|
submenuVisible: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
activeItemPath(newPath) {
|
||||||
|
if (ObjectUtils.isNotEmpty(newPath)) {
|
||||||
|
this.bindOutsideClickListener();
|
||||||
|
this.bindResizeListener();
|
||||||
|
this.bindDocumentContextMenuListener();
|
||||||
|
} else if (!this.visible) {
|
||||||
|
this.unbindOutsideClickListener();
|
||||||
|
this.unbindResizeListener();
|
||||||
|
this.unbindDocumentContextMenuListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.unbindResizeListener();
|
this.unbindResizeListener();
|
||||||
this.unbindOutsideClickListener();
|
this.unbindOutsideClickListener();
|
||||||
|
@ -63,6 +118,7 @@ export default {
|
||||||
ZIndexUtils.clear(this.container);
|
ZIndexUtils.clear(this.container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.target = null;
|
||||||
this.container = null;
|
this.container = null;
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -71,53 +127,276 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
itemClick(event) {
|
getItemProp(item, name) {
|
||||||
const item = event.item;
|
return item ? ObjectUtils.getItemValue(item[name]) : undefined;
|
||||||
|
},
|
||||||
if (item.command) {
|
getItemLabel(item) {
|
||||||
item.command(event);
|
return this.getItemProp(item, 'label');
|
||||||
event.originalEvent.preventDefault();
|
},
|
||||||
}
|
isItemDisabled(item) {
|
||||||
|
return this.getItemProp(item, 'disabled');
|
||||||
this.hide();
|
},
|
||||||
|
isItemGroup(item) {
|
||||||
|
return ObjectUtils.isNotEmpty(this.getItemProp(item, 'items'));
|
||||||
|
},
|
||||||
|
isItemSeparator(item) {
|
||||||
|
return this.getItemProp(item, 'separator');
|
||||||
|
},
|
||||||
|
getProccessedItemLabel(processedItem) {
|
||||||
|
return processedItem ? this.getItemLabel(processedItem.item) : undefined;
|
||||||
|
},
|
||||||
|
isProccessedItemGroup(processedItem) {
|
||||||
|
return processedItem && ObjectUtils.isNotEmpty(processedItem.items);
|
||||||
},
|
},
|
||||||
toggle(event) {
|
toggle(event) {
|
||||||
if (this.visible) this.hide();
|
this.visible ? this.hide() : this.show(event);
|
||||||
else this.show(event);
|
|
||||||
},
|
|
||||||
onLeafClick() {
|
|
||||||
this.hide();
|
|
||||||
},
|
},
|
||||||
show(event) {
|
show(event) {
|
||||||
|
this.activeItemPath = [];
|
||||||
|
this.focusedItemInfo = { index: -1, level: 0, parentKey: '' };
|
||||||
|
DomHandler.focus(this.list);
|
||||||
|
|
||||||
this.pageX = event.pageX;
|
this.pageX = event.pageX;
|
||||||
this.pageY = event.pageY;
|
this.pageY = event.pageY;
|
||||||
|
this.visible ? this.position() : (this.visible = true);
|
||||||
if (this.visible) this.position();
|
|
||||||
else this.visible = true;
|
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
hide() {
|
hide() {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
|
this.activeItemPath = [];
|
||||||
|
this.focusedItemInfo = { index: -1, level: 0, parentKey: '' };
|
||||||
|
},
|
||||||
|
onFocus(event) {
|
||||||
|
this.focused = true;
|
||||||
|
this.focusedItemInfo = this.focusedItemInfo.index !== -1 ? this.focusedItemInfo : { index: -1, level: 0, parentKey: '' };
|
||||||
|
this.$emit('focus', event);
|
||||||
|
},
|
||||||
|
onBlur(event) {
|
||||||
|
this.focused = false;
|
||||||
|
this.focusedItemInfo = { index: -1, level: 0, parentKey: '' };
|
||||||
|
this.searchValue = '';
|
||||||
|
this.$emit('blur', event);
|
||||||
|
},
|
||||||
|
onKeyDown(event) {
|
||||||
|
const metaKey = event.metaKey || event.ctrlKey;
|
||||||
|
|
||||||
|
switch (event.code) {
|
||||||
|
case 'ArrowDown':
|
||||||
|
this.onArrowDownKey(event);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ArrowUp':
|
||||||
|
this.onArrowUpKey(event);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ArrowLeft':
|
||||||
|
this.onArrowLeftKey(event);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ArrowRight':
|
||||||
|
this.onArrowRightKey(event);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Home':
|
||||||
|
this.onHomeKey(event);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'End':
|
||||||
|
this.onEndKey(event);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Space':
|
||||||
|
this.onSpaceKey(event);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Enter':
|
||||||
|
this.onEnterKey(event);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Escape':
|
||||||
|
this.onEscapeKey(event);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Tab':
|
||||||
|
this.onTabKey(event);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'PageDown':
|
||||||
|
case 'PageUp':
|
||||||
|
case 'Backspace':
|
||||||
|
case 'ShiftLeft':
|
||||||
|
case 'ShiftRight':
|
||||||
|
//NOOP
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (!metaKey && ObjectUtils.isPrintableCharacter(event.key)) {
|
||||||
|
this.searchItems(event, event.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onItemChange(event) {
|
||||||
|
const { processedItem, isFocus } = event;
|
||||||
|
|
||||||
|
if (ObjectUtils.isEmpty(processedItem)) return;
|
||||||
|
|
||||||
|
const { index, key, level, parentKey, items } = processedItem;
|
||||||
|
const grouped = ObjectUtils.isNotEmpty(items);
|
||||||
|
const activeItemPath = this.activeItemPath.filter((p) => p.parentKey !== parentKey && p.parentKey !== key);
|
||||||
|
|
||||||
|
if (grouped) {
|
||||||
|
activeItemPath.push(processedItem);
|
||||||
|
this.submenuVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.focusedItemInfo = { index, level, parentKey };
|
||||||
|
this.activeItemPath = activeItemPath;
|
||||||
|
|
||||||
|
isFocus && DomHandler.focus(this.list);
|
||||||
|
},
|
||||||
|
onItemClick(event) {
|
||||||
|
const { processedItem } = event;
|
||||||
|
const grouped = this.isProccessedItemGroup(processedItem);
|
||||||
|
const selected = this.isSelected(processedItem);
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
const { index, key, level, parentKey } = processedItem;
|
||||||
|
|
||||||
|
this.activeItemPath = this.activeItemPath.filter((p) => key !== p.key && key.startsWith(p.key));
|
||||||
|
this.focusedItemInfo = { index, level, parentKey };
|
||||||
|
|
||||||
|
DomHandler.focus(this.list);
|
||||||
|
} else {
|
||||||
|
grouped ? this.onItemChange(event) : this.hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onItemMouseEnter(event) {
|
||||||
|
this.onItemChange(event);
|
||||||
|
},
|
||||||
|
onArrowDownKey(event) {
|
||||||
|
const itemIndex = this.focusedItemInfo.index !== -1 ? this.findNextItemIndex(this.focusedItemInfo.index) : this.findFirstFocusedItemIndex();
|
||||||
|
|
||||||
|
this.changeFocusedItemIndex(event, itemIndex);
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
onArrowUpKey(event) {
|
||||||
|
if (event.altKey) {
|
||||||
|
if (this.focusedItemInfo.index !== -1) {
|
||||||
|
const processedItem = this.visibleItems[this.focusedItemInfo.index];
|
||||||
|
const grouped = this.isProccessedItemGroup(processedItem);
|
||||||
|
|
||||||
|
!grouped && this.onItemChange({ originalEvent: event, processedItem });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.popup && this.hide();
|
||||||
|
event.preventDefault();
|
||||||
|
} else {
|
||||||
|
const itemIndex = this.focusedItemInfo.index !== -1 ? this.findPrevItemIndex(this.focusedItemInfo.index) : this.findLastFocusedItemIndex();
|
||||||
|
|
||||||
|
this.changeFocusedItemIndex(event, itemIndex);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onArrowLeftKey(event) {
|
||||||
|
const processedItem = this.visibleItems[this.focusedItemInfo.index];
|
||||||
|
const parentItem = this.activeItemPath.find((p) => p.key === processedItem.parentKey);
|
||||||
|
const root = ObjectUtils.isEmpty(processedItem.parent);
|
||||||
|
|
||||||
|
if (!root) {
|
||||||
|
this.focusedItemInfo = { index: -1, parentKey: parentItem ? parentItem.parentKey : '' };
|
||||||
|
this.searchValue = '';
|
||||||
|
this.onArrowDownKey(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activeItemPath = this.activeItemPath.filter((p) => p.parentKey !== this.focusedItemInfo.parentKey);
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
onArrowRightKey(event) {
|
||||||
|
const processedItem = this.visibleItems[this.focusedItemInfo.index];
|
||||||
|
const grouped = this.isProccessedItemGroup(processedItem);
|
||||||
|
|
||||||
|
if (grouped) {
|
||||||
|
this.onItemChange({ originalEvent: event, processedItem });
|
||||||
|
this.focusedItemInfo = { index: -1, parentKey: processedItem.key };
|
||||||
|
this.searchValue = '';
|
||||||
|
this.onArrowDownKey(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
onHomeKey(event) {
|
||||||
|
this.changeFocusedItemIndex(event, this.findFirstItemIndex());
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
onEndKey(event) {
|
||||||
|
this.changeFocusedItemIndex(event, this.findLastItemIndex());
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
onEnterKey(event) {
|
||||||
|
if (this.focusedItemInfo.index !== -1) {
|
||||||
|
const element = DomHandler.findSingle(this.list, `li[id="${`${this.focusedItemId}`}"]`);
|
||||||
|
const anchorElement = element && DomHandler.findSingle(element, '.p-menuitem-link');
|
||||||
|
|
||||||
|
anchorElement ? anchorElement.click() : element && element.click();
|
||||||
|
const processedItem = this.visibleItems[this.focusedItemInfo.index];
|
||||||
|
const grouped = this.isProccessedItemGroup(processedItem);
|
||||||
|
|
||||||
|
!grouped && (this.focusedItemInfo.index = this.findFirstFocusedItemIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
onSpaceKey(event) {
|
||||||
|
this.onEnterKey(event);
|
||||||
|
},
|
||||||
|
onEscapeKey(event) {
|
||||||
|
this.hide();
|
||||||
|
!this.popup && (this.focusedItemInfo.index = this.findFirstFocusedItemIndex());
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
onTabKey(event) {
|
||||||
|
if (this.focusedItemInfo.index !== -1) {
|
||||||
|
const processedItem = this.visibleItems[this.focusedItemInfo.index];
|
||||||
|
const grouped = this.isProccessedItemGroup(processedItem);
|
||||||
|
|
||||||
|
!grouped && this.onItemChange({ originalEvent: event, processedItem });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hide();
|
||||||
},
|
},
|
||||||
onEnter(el) {
|
onEnter(el) {
|
||||||
this.position();
|
this.position();
|
||||||
this.bindOutsideClickListener();
|
|
||||||
this.bindResizeListener();
|
|
||||||
|
|
||||||
if (this.autoZIndex) {
|
if (this.autoZIndex) {
|
||||||
ZIndexUtils.set('menu', el, this.baseZIndex + this.$primevue.config.zIndex.menu);
|
ZIndexUtils.set('menu', el, this.baseZIndex + this.$primevue.config.zIndex.menu);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onAfterEnter() {
|
||||||
|
this.bindOutsideClickListener();
|
||||||
|
this.bindResizeListener();
|
||||||
|
this.bindDocumentContextMenuListener();
|
||||||
|
|
||||||
|
this.$emit('show');
|
||||||
|
DomHandler.focus(this.list);
|
||||||
|
},
|
||||||
onLeave() {
|
onLeave() {
|
||||||
this.unbindOutsideClickListener();
|
this.$emit('hide');
|
||||||
this.unbindResizeListener();
|
this.container = null;
|
||||||
},
|
},
|
||||||
onAfterLeave(el) {
|
onAfterLeave(el) {
|
||||||
if (this.autoZIndex) {
|
if (this.autoZIndex) {
|
||||||
ZIndexUtils.clear(el);
|
ZIndexUtils.clear(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.unbindOutsideClickListener();
|
||||||
|
this.unbindResizeListener();
|
||||||
|
this.unbindDocumentContextMenuListener();
|
||||||
},
|
},
|
||||||
position() {
|
position() {
|
||||||
let left = this.pageX + 1;
|
let left = this.pageX + 1;
|
||||||
|
@ -152,7 +431,10 @@ export default {
|
||||||
bindOutsideClickListener() {
|
bindOutsideClickListener() {
|
||||||
if (!this.outsideClickListener) {
|
if (!this.outsideClickListener) {
|
||||||
this.outsideClickListener = (event) => {
|
this.outsideClickListener = (event) => {
|
||||||
if (this.visible && this.container && !this.container.contains(event.target) && !event.ctrlKey) {
|
const isOutsideContainer = this.container && !this.container.contains(event.target);
|
||||||
|
const isOutsideTarget = this.visible ? !(this.target && (this.target === event.target || this.target.contains(event.target))) : true;
|
||||||
|
|
||||||
|
if (isOutsideContainer && isOutsideTarget) {
|
||||||
this.hide();
|
this.hide();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -186,7 +468,7 @@ export default {
|
||||||
bindDocumentContextMenuListener() {
|
bindDocumentContextMenuListener() {
|
||||||
if (!this.documentContextMenuListener) {
|
if (!this.documentContextMenuListener) {
|
||||||
this.documentContextMenuListener = (event) => {
|
this.documentContextMenuListener = (event) => {
|
||||||
this.show(event);
|
event.button !== 2 ? this.show(event) : this.hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('contextmenu', this.documentContextMenuListener);
|
document.addEventListener('contextmenu', this.documentContextMenuListener);
|
||||||
|
@ -198,19 +480,142 @@ export default {
|
||||||
this.documentContextMenuListener = null;
|
this.documentContextMenuListener = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
isItemMatched(processedItem) {
|
||||||
|
return this.isValidItem(processedItem) && this.getProccessedItemLabel(processedItem).toLocaleLowerCase().startsWith(this.searchValue.toLocaleLowerCase());
|
||||||
|
},
|
||||||
|
isValidItem(processedItem) {
|
||||||
|
return !!processedItem && !this.isItemDisabled(processedItem.item) && !this.isItemSeparator(processedItem.item);
|
||||||
|
},
|
||||||
|
isValidSelectedItem(processedItem) {
|
||||||
|
return this.isValidItem(processedItem) && this.isSelected(processedItem);
|
||||||
|
},
|
||||||
|
isSelected(processedItem) {
|
||||||
|
return this.activeItemPath.some((p) => p.key === processedItem.key);
|
||||||
|
},
|
||||||
|
findFirstItemIndex() {
|
||||||
|
return this.visibleItems.findIndex((processedItem) => this.isValidItem(processedItem));
|
||||||
|
},
|
||||||
|
findLastItemIndex() {
|
||||||
|
return ObjectUtils.findLastIndex(this.visibleItems, (processedItem) => this.isValidItem(processedItem));
|
||||||
|
},
|
||||||
|
findNextItemIndex(index) {
|
||||||
|
const matchedItemIndex = index < this.visibleItems.length - 1 ? this.visibleItems.slice(index + 1).findIndex((processedItem) => this.isValidItem(processedItem)) : -1;
|
||||||
|
|
||||||
|
return matchedItemIndex > -1 ? matchedItemIndex + index + 1 : index;
|
||||||
|
},
|
||||||
|
findPrevItemIndex(index) {
|
||||||
|
const matchedItemIndex = index > 0 ? ObjectUtils.findLastIndex(this.visibleItems.slice(0, index), (processedItem) => this.isValidItem(processedItem)) : -1;
|
||||||
|
|
||||||
|
return matchedItemIndex > -1 ? matchedItemIndex : index;
|
||||||
|
},
|
||||||
|
findSelectedItemIndex() {
|
||||||
|
return this.visibleItems.findIndex((processedItem) => this.isValidSelectedItem(processedItem));
|
||||||
|
},
|
||||||
|
findFirstFocusedItemIndex() {
|
||||||
|
const selectedIndex = this.findSelectedItemIndex();
|
||||||
|
|
||||||
|
return selectedIndex < 0 ? this.findFirstItemIndex() : selectedIndex;
|
||||||
|
},
|
||||||
|
findLastFocusedItemIndex() {
|
||||||
|
const selectedIndex = this.findSelectedItemIndex();
|
||||||
|
|
||||||
|
return selectedIndex < 0 ? this.findLastItemIndex() : selectedIndex;
|
||||||
|
},
|
||||||
|
searchItems(event, char) {
|
||||||
|
this.searchValue = (this.searchValue || '') + char;
|
||||||
|
|
||||||
|
let itemIndex = -1;
|
||||||
|
let matched = false;
|
||||||
|
|
||||||
|
if (this.focusedItemInfo.index !== -1) {
|
||||||
|
itemIndex = this.visibleItems.slice(this.focusedItemInfo.index).findIndex((processedItem) => this.isItemMatched(processedItem));
|
||||||
|
itemIndex = itemIndex === -1 ? this.visibleItems.slice(0, this.focusedItemInfo.index).findIndex((processedItem) => this.isItemMatched(processedItem)) : itemIndex + this.focusedItemInfo.index;
|
||||||
|
} else {
|
||||||
|
itemIndex = this.visibleItems.findIndex((processedItem) => this.isItemMatched(processedItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex !== -1) {
|
||||||
|
matched = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex === -1 && this.focusedItemInfo.index === -1) {
|
||||||
|
itemIndex = this.findFirstFocusedItemIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemIndex !== -1) {
|
||||||
|
this.changeFocusedItemIndex(event, itemIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.searchTimeout) {
|
||||||
|
clearTimeout(this.searchTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.searchTimeout = setTimeout(() => {
|
||||||
|
this.searchValue = '';
|
||||||
|
this.searchTimeout = null;
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
return matched;
|
||||||
|
},
|
||||||
|
changeFocusedItemIndex(event, index) {
|
||||||
|
if (this.focusedItemInfo.index !== index) {
|
||||||
|
this.focusedItemInfo.index = index;
|
||||||
|
this.scrollInView();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scrollInView(index = -1) {
|
||||||
|
const id = index !== -1 ? `${this.id}_${index}` : this.focusedItemId;
|
||||||
|
const element = DomHandler.findSingle(this.list, `li[id="${id}"]`);
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView && element.scrollIntoView({ block: 'nearest', inline: 'start' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createProcessedItems(items, level = 0, parent = {}, parentKey = '') {
|
||||||
|
const processedItems = [];
|
||||||
|
|
||||||
|
items &&
|
||||||
|
items.forEach((item, index) => {
|
||||||
|
const key = (parentKey !== '' ? parentKey + '_' : '') + index;
|
||||||
|
const newItem = {
|
||||||
|
item,
|
||||||
|
index,
|
||||||
|
level,
|
||||||
|
key,
|
||||||
|
parent,
|
||||||
|
parentKey
|
||||||
|
};
|
||||||
|
|
||||||
|
newItem['items'] = this.createProcessedItems(item.items, level + 1, newItem, key);
|
||||||
|
processedItems.push(newItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
return processedItems;
|
||||||
|
},
|
||||||
containerRef(el) {
|
containerRef(el) {
|
||||||
this.container = el;
|
this.container = el;
|
||||||
|
},
|
||||||
|
listRef(el) {
|
||||||
|
this.list = el ? el.$el : undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
containerClass() {
|
containerClass() {
|
||||||
return [
|
return ['p-contextmenu p-component', { 'p-input-filled': this.$primevue.config.inputStyle === 'filled', 'p-ripple-disabled': this.$primevue.config.ripple === false }];
|
||||||
'p-contextmenu p-component',
|
},
|
||||||
{
|
processedItems() {
|
||||||
'p-input-filled': this.$primevue.config.inputStyle === 'filled',
|
return this.createProcessedItems(this.model || []);
|
||||||
'p-ripple-disabled': this.$primevue.config.ripple === false
|
},
|
||||||
}
|
visibleItems() {
|
||||||
];
|
const processedItem = this.activeItemPath.find((p) => p.key === this.focusedItemInfo.parentKey);
|
||||||
|
|
||||||
|
return processedItem ? processedItem.items : this.processedItems;
|
||||||
|
},
|
||||||
|
id() {
|
||||||
|
return this.$attrs.id || UniqueComponentId();
|
||||||
|
},
|
||||||
|
focusedItemId() {
|
||||||
|
return this.focusedItemInfo.index !== -1 ? `${this.id}${ObjectUtils.isNotEmpty(this.focusedItemInfo.parentKey) ? '_' + this.focusedItemInfo.parentKey : ''}_${this.focusedItemInfo.index}` : null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -1,61 +1,92 @@
|
||||||
<template>
|
<template>
|
||||||
<transition name="p-contextmenusub" @enter="onEnter">
|
<transition name="p-contextmenusub" @enter="onEnter">
|
||||||
<ul v-if="root ? true : parentActive" ref="container" :class="containerClass" role="menu">
|
<ul v-if="root ? true : visible" ref="container">
|
||||||
<template v-for="(item, i) of model" :key="label(item) + i.toString()">
|
<template v-for="(processedItem, index) of items" :key="getItemKey(processedItem)">
|
||||||
<li v-if="visible(item) && !item.separator" role="none" :class="getItemClass(item)" :style="item.style" @mouseenter="onItemMouseEnter($event, item)">
|
<li
|
||||||
<template v-if="!template">
|
v-if="isItemVisible(processedItem) && !getItemProp(processedItem, 'separator')"
|
||||||
<router-link v-if="item.to && !disabled(item)" v-slot="{ navigate, href, isActive, isExactActive }" :to="item.to" custom>
|
:id="getItemId(processedItem)"
|
||||||
<a v-ripple :href="href" @click="onItemClick($event, item, navigate)" :class="linkClass(item, { isActive, isExactActive })" role="menuitem">
|
:style="getItemProp(processedItem, 'style')"
|
||||||
<span v-if="item.icon" :class="['p-menuitem-icon', item.icon]"></span>
|
:class="getItemClass(processedItem)"
|
||||||
<span class="p-menuitem-text">{{ label(item) }}</span>
|
role="menuitem"
|
||||||
|
:aria-label="getItemLabel(processedItem)"
|
||||||
|
:aria-disabled="isItemDisabled(processedItem) || undefined"
|
||||||
|
:aria-expanded="isItemGroup(processedItem) ? isItemActive(processedItem) : undefined"
|
||||||
|
:aria-haspopup="isItemGroup(processedItem) && !getItemProp(processedItem, 'to') ? 'menu' : undefined"
|
||||||
|
:aria-level="level + 1"
|
||||||
|
:aria-setsize="getAriaSetSize()"
|
||||||
|
:aria-posinset="getAriaPosInset(index)"
|
||||||
|
>
|
||||||
|
<div class="p-menuitem-content" @click="onItemClick($event, processedItem)" @mouseenter="onItemMouseEnter($event, processedItem)">
|
||||||
|
<template v-if="!template">
|
||||||
|
<router-link v-if="getItemProp(processedItem, 'to') && !isItemDisabled(processedItem)" v-slot="{ navigate, href, isActive, isExactActive }" :to="getItemProp(processedItem, 'to')" custom>
|
||||||
|
<a v-ripple :href="href" :class="getItemActionClass(processedItem, { isActive, isExactActive })" tabindex="-1" aria-hidden="true" @click="onItemActionClick($event, navigate)">
|
||||||
|
<span v-if="getItemProp(processedItem, 'icon')" :class="getItemIconClass(processedItem)"></span>
|
||||||
|
<span class="p-menuitem-text">{{ getItemLabel(processedItem) }}</span>
|
||||||
|
</a>
|
||||||
|
</router-link>
|
||||||
|
<a v-else v-ripple :href="getItemProp(processedItem, 'url')" :class="getItemActionClass(processedItem)" :target="getItemProp(processedItem, 'target')" tabindex="-1" aria-hidden="true">
|
||||||
|
<span v-if="getItemProp(processedItem, 'icon')" :class="getItemIconClass(processedItem)"></span>
|
||||||
|
<span class="p-menuitem-text">{{ getItemLabel(processedItem) }}</span>
|
||||||
|
<span v-if="getItemProp(processedItem, 'items')" class="p-submenu-icon pi pi-angle-right"></span>
|
||||||
</a>
|
</a>
|
||||||
</router-link>
|
</template>
|
||||||
<a
|
<component v-else :is="template" :item="processedItem.item"></component>
|
||||||
v-else
|
</div>
|
||||||
v-ripple
|
<ContextMenuSub
|
||||||
:href="item.url"
|
v-if="isItemVisible(processedItem) && isItemGroup(processedItem)"
|
||||||
:class="linkClass(item)"
|
:id="getItemId(processedItem) + '_list'"
|
||||||
:target="item.target"
|
role="menu"
|
||||||
@click="onItemClick($event, item)"
|
class="p-submenu-list"
|
||||||
:aria-haspopup="item.items != null"
|
:menuId="menuId"
|
||||||
:aria-expanded="item === activeItem"
|
:focusedItemId="focusedItemId"
|
||||||
role="menuitem"
|
:items="processedItem.items"
|
||||||
:tabindex="disabled(item) ? null : '0'"
|
:template="template"
|
||||||
>
|
:activeItemPath="activeItemPath"
|
||||||
<span v-if="item.icon" :class="['p-menuitem-icon', item.icon]"></span>
|
:exact="exact"
|
||||||
<span class="p-menuitem-text">{{ label(item) }}</span>
|
:level="level + 1"
|
||||||
<span v-if="item.items" class="p-submenu-icon pi pi-angle-right"></span>
|
:visible="isItemActive(processedItem) && isItemGroup(processedItem)"
|
||||||
</a>
|
@item-click="$emit('item-click', $event)"
|
||||||
</template>
|
@item-mouseenter="$emit('item-mouseenter', $event)"
|
||||||
<component v-else :is="template" :item="item"></component>
|
/>
|
||||||
<ContextMenuSub v-if="visible(item) && item.items" :key="label(item) + '_sub_'" :model="item.items" :template="template" @leaf-click="onLeafClick" :parentActive="item === activeItem" :exact="exact" />
|
|
||||||
</li>
|
</li>
|
||||||
<li v-if="visible(item) && item.separator" :key="'separator' + i.toString()" :class="['p-menu-separator', item.class]" :style="item.style" role="separator"></li>
|
<li v-if="isItemVisible(processedItem) && getItemProp(processedItem, 'separator')" :id="getItemId(processedItem)" :style="getItemProp(processedItem, 'style')" :class="getSeparatorItemClass(processedItem)" role="separator"></li>
|
||||||
</template>
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { DomHandler } from 'primevue/utils';
|
|
||||||
import Ripple from 'primevue/ripple';
|
import Ripple from 'primevue/ripple';
|
||||||
|
import { DomHandler, ObjectUtils } from 'primevue/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ContextMenuSub',
|
name: 'ContextMenuSub',
|
||||||
emits: ['leaf-click'],
|
emits: ['item-click', 'item-mouseenter'],
|
||||||
props: {
|
props: {
|
||||||
model: {
|
items: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
menuId: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
focusedItemId: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
root: {
|
root: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
parentActive: {
|
visible: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
level: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
template: {
|
template: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: null
|
default: null
|
||||||
|
@ -63,60 +94,57 @@ export default {
|
||||||
exact: {
|
exact: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
}
|
},
|
||||||
},
|
activeItemPath: {
|
||||||
data() {
|
type: Object,
|
||||||
return {
|
default: null
|
||||||
activeItem: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
parentActive(newValue) {
|
|
||||||
if (!newValue) {
|
|
||||||
this.activeItem = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onItemMouseEnter(event, item) {
|
getItemId(processedItem) {
|
||||||
if (this.disabled(item)) {
|
return `${this.menuId}_${processedItem.key}`;
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.activeItem = item;
|
|
||||||
},
|
},
|
||||||
onItemClick(event, item, navigate) {
|
getItemKey(processedItem) {
|
||||||
if (this.disabled(item)) {
|
return this.getItemId(processedItem);
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.command) {
|
|
||||||
item.command({
|
|
||||||
originalEvent: event,
|
|
||||||
item: item
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.items) {
|
|
||||||
if (this.activeItem && item === this.activeItem) this.activeItem = null;
|
|
||||||
else this.activeItem = item;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!item.items) {
|
|
||||||
this.onLeafClick();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.to && navigate) {
|
|
||||||
navigate(event);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onLeafClick() {
|
getItemProp(processedItem, name) {
|
||||||
this.activeItem = null;
|
return processedItem && processedItem.item ? ObjectUtils.getItemValue(processedItem.item[name]) : undefined;
|
||||||
this.$emit('leaf-click');
|
},
|
||||||
|
getItemLabel(processedItem) {
|
||||||
|
return this.getItemProp(processedItem, 'label');
|
||||||
|
},
|
||||||
|
isItemActive(processedItem) {
|
||||||
|
return this.activeItemPath.some((path) => path.key === processedItem.key);
|
||||||
|
},
|
||||||
|
isItemVisible(processedItem) {
|
||||||
|
return this.getItemProp(processedItem, 'visible') !== false;
|
||||||
|
},
|
||||||
|
isItemDisabled(processedItem) {
|
||||||
|
return this.getItemProp(processedItem, 'disabled');
|
||||||
|
},
|
||||||
|
isItemFocused(processedItem) {
|
||||||
|
return this.focusedItemId === this.getItemId(processedItem);
|
||||||
|
},
|
||||||
|
isItemGroup(processedItem) {
|
||||||
|
return ObjectUtils.isNotEmpty(processedItem.items);
|
||||||
|
},
|
||||||
|
onItemClick(event, processedItem) {
|
||||||
|
const command = this.getItemProp(processedItem, 'command');
|
||||||
|
|
||||||
|
command && command({ originalEvent: event, item: processedItem.item });
|
||||||
|
this.$emit('item-click', { originalEvent: event, processedItem, isFocus: true });
|
||||||
|
},
|
||||||
|
onItemMouseEnter(event, processedItem) {
|
||||||
|
this.$emit('item-mouseenter', { originalEvent: event, processedItem });
|
||||||
|
},
|
||||||
|
onItemActionClick(event, navigate) {
|
||||||
|
navigate && navigate(event);
|
||||||
|
},
|
||||||
|
getAriaSetSize() {
|
||||||
|
return this.items.filter((processedItem) => this.isItemVisible(processedItem) && !this.getItemProp(processedItem, 'separator')).length;
|
||||||
|
},
|
||||||
|
getAriaPosInset(index) {
|
||||||
|
return index - this.items.slice(0, index).filter((processedItem) => this.isItemVisible(processedItem) && this.getItemProp(processedItem, 'separator')).length + 1;
|
||||||
},
|
},
|
||||||
onEnter() {
|
onEnter() {
|
||||||
this.position();
|
this.position();
|
||||||
|
@ -136,38 +164,31 @@ export default {
|
||||||
this.$refs.container.style.left = itemOuterWidth + 'px';
|
this.$refs.container.style.left = itemOuterWidth + 'px';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getItemClass(item) {
|
getItemClass(processedItem) {
|
||||||
return [
|
return [
|
||||||
'p-menuitem',
|
'p-menuitem',
|
||||||
item.class,
|
this.getItemProp(processedItem, 'class'),
|
||||||
{
|
{
|
||||||
'p-menuitem-active': this.activeItem === item
|
'p-menuitem-active p-highlight': this.isItemActive(processedItem),
|
||||||
|
'p-focus': this.isItemFocused(processedItem),
|
||||||
|
'p-disabled': this.isItemDisabled(processedItem)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
linkClass(item, routerProps) {
|
getItemActionClass(processedItem, routerProps) {
|
||||||
return [
|
return [
|
||||||
'p-menuitem-link',
|
'p-menuitem-link',
|
||||||
{
|
{
|
||||||
'p-disabled': this.disabled(item),
|
|
||||||
'router-link-active': routerProps && routerProps.isActive,
|
'router-link-active': routerProps && routerProps.isActive,
|
||||||
'router-link-active-exact': this.exact && routerProps && routerProps.isExactActive
|
'router-link-active-exact': this.exact && routerProps && routerProps.isExactActive
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
visible(item) {
|
getItemIconClass(processedItem) {
|
||||||
return typeof item.visible === 'function' ? item.visible() : item.visible !== false;
|
return ['p-menuitem-icon', this.getItemProp(processedItem, 'icon')];
|
||||||
},
|
},
|
||||||
disabled(item) {
|
getSeparatorItemClass(processedItem) {
|
||||||
return typeof item.disabled === 'function' ? item.disabled() : item.disabled;
|
return ['p-menuitem-separator', this.getItemProp(processedItem, 'class')];
|
||||||
},
|
|
||||||
label(item) {
|
|
||||||
return typeof item.label === 'function' ? item.label() : item.label;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
containerClass() {
|
|
||||||
return { 'p-submenu-list': !this.root };
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
|
|
|
@ -18,25 +18,25 @@
|
||||||
/>
|
/>
|
||||||
<component v-else-if="column.children && column.children.body && !column.children.editor && d_editing" :is="column.children.body" :data="editingRowData" :column="column" :field="field" :index="rowIndex" :frozenRow="frozenRow" />
|
<component v-else-if="column.children && column.children.body && !column.children.editor && d_editing" :is="column.children.body" :data="editingRowData" :column="column" :field="field" :index="rowIndex" :frozenRow="frozenRow" />
|
||||||
<template v-else-if="columnProp('selectionMode')">
|
<template v-else-if="columnProp('selectionMode')">
|
||||||
<DTRadioButton v-if="columnProp('selectionMode') === 'single'" :value="rowData" :checked="selected" @change="toggleRowWithRadio($event, rowIndex)" />
|
<DTRadioButton v-if="columnProp('selectionMode') === 'single'" :value="rowData" :name="name" :checked="selected" @change="toggleRowWithRadio($event, rowIndex)" />
|
||||||
<DTCheckbox v-else-if="columnProp('selectionMode') === 'multiple'" :value="rowData" :checked="selected" @change="toggleRowWithCheckbox($event, rowIndex)" />
|
<DTCheckbox v-else-if="columnProp('selectionMode') === 'multiple'" :value="rowData" :checked="selected" :aria-selected="selected ? true : undefined" @change="toggleRowWithCheckbox($event, rowIndex)" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="columnProp('rowReorder')">
|
<template v-else-if="columnProp('rowReorder')">
|
||||||
<i :class="['p-datatable-reorderablerow-handle', columnProp('rowReorderIcon') || 'pi pi-bars']"></i>
|
<i :class="['p-datatable-reorderablerow-handle', columnProp('rowReorderIcon') || 'pi pi-bars']"></i>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="columnProp('expander')">
|
<template v-else-if="columnProp('expander')">
|
||||||
<button v-ripple class="p-row-toggler p-link" @click="toggleRow" type="button">
|
<button v-ripple class="p-row-toggler p-link" type="button" :aria-expanded="isRowExpanded" :aria-controls="ariaControls" :aria-label="expandButtonAriaLabel" @click="toggleRow">
|
||||||
<span :class="rowTogglerIcon"></span>
|
<span :class="rowTogglerIcon"></span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="editMode === 'row' && columnProp('rowEditor')">
|
<template v-else-if="editMode === 'row' && columnProp('rowEditor')">
|
||||||
<button v-if="!d_editing" v-ripple class="p-row-editor-init p-link" @click="onRowEditInit" type="button">
|
<button v-if="!d_editing" v-ripple class="p-row-editor-init p-link" type="button" :aria-label="initButtonAriaLabel" @click="onRowEditInit">
|
||||||
<span class="p-row-editor-init-icon pi pi-fw pi-pencil"></span>
|
<span class="p-row-editor-init-icon pi pi-fw pi-pencil"></span>
|
||||||
</button>
|
</button>
|
||||||
<button v-if="d_editing" v-ripple class="p-row-editor-save p-link" @click="onRowEditSave" type="button">
|
<button v-if="d_editing" v-ripple class="p-row-editor-save p-link" type="button" :aria-label="saveButtonAriaLabel" @click="onRowEditSave">
|
||||||
<span class="p-row-editor-save-icon pi pi-fw pi-check"></span>
|
<span class="p-row-editor-save-icon pi pi-fw pi-check"></span>
|
||||||
</button>
|
</button>
|
||||||
<button v-if="d_editing" v-ripple class="p-row-editor-cancel p-link" @click="onRowEditCancel" type="button">
|
<button v-if="d_editing" v-ripple class="p-row-editor-cancel p-link" type="button" :aria-label="cancelButtonAriaLabel" @click="onRowEditCancel">
|
||||||
<span class="p-row-editor-cancel-icon pi pi-fw pi-times"></span>
|
<span class="p-row-editor-cancel-icon pi pi-fw pi-times"></span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
@ -45,11 +45,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { DomHandler, ObjectUtils } from 'primevue/utils';
|
|
||||||
import OverlayEventBus from 'primevue/overlayeventbus';
|
import OverlayEventBus from 'primevue/overlayeventbus';
|
||||||
import RowRadioButton from './RowRadioButton.vue';
|
|
||||||
import RowCheckbox from './RowCheckbox.vue';
|
|
||||||
import Ripple from 'primevue/ripple';
|
import Ripple from 'primevue/ripple';
|
||||||
|
import { DomHandler, ObjectUtils } from 'primevue/utils';
|
||||||
|
import RowCheckbox from './RowCheckbox.vue';
|
||||||
|
import RowRadioButton from './RowRadioButton.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'BodyCell',
|
name: 'BodyCell',
|
||||||
|
@ -102,6 +102,14 @@ export default {
|
||||||
virtualScrollerContentProps: {
|
virtualScrollerContentProps: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
ariaControls: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
documentEditListener: null,
|
documentEditListener: null,
|
||||||
|
@ -110,7 +118,8 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
d_editing: this.editing,
|
d_editing: this.editing,
|
||||||
styleObject: {}
|
styleObject: {},
|
||||||
|
isRowExpanded: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -151,6 +160,7 @@ export default {
|
||||||
return ObjectUtils.resolveFieldData(this.rowData, this.field);
|
return ObjectUtils.resolveFieldData(this.rowData, this.field);
|
||||||
},
|
},
|
||||||
toggleRow(event) {
|
toggleRow(event) {
|
||||||
|
this.isRowExpanded = !this.isRowExpanded;
|
||||||
this.$emit('row-toggle', {
|
this.$emit('row-toggle', {
|
||||||
originalEvent: event,
|
originalEvent: event,
|
||||||
data: this.rowData
|
data: this.rowData
|
||||||
|
@ -234,22 +244,25 @@ export default {
|
||||||
},
|
},
|
||||||
onKeyDown(event) {
|
onKeyDown(event) {
|
||||||
if (this.editMode === 'cell') {
|
if (this.editMode === 'cell') {
|
||||||
switch (event.which) {
|
switch (event.code) {
|
||||||
case 13:
|
case 'Enter':
|
||||||
this.completeEdit(event, 'enter');
|
this.completeEdit(event, 'enter');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 27:
|
case 'Escape':
|
||||||
this.switchCellToViewMode();
|
this.switchCellToViewMode();
|
||||||
this.$emit('cell-edit-cancel', { originalEvent: event, data: this.rowData, field: this.field, index: this.rowIndex });
|
this.$emit('cell-edit-cancel', { originalEvent: event, data: this.rowData, field: this.field, index: this.rowIndex });
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 9:
|
case 'Tab':
|
||||||
this.completeEdit(event, 'tab');
|
this.completeEdit(event, 'tab');
|
||||||
|
|
||||||
if (event.shiftKey) this.moveToPreviousCell(event);
|
if (event.shiftKey) this.moveToPreviousCell(event);
|
||||||
else this.moveToNextCell(event);
|
else this.moveToNextCell(event);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -422,6 +435,18 @@ export default {
|
||||||
field: this.field
|
field: this.field
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
expandButtonAriaLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? (this.isRowExpanded ? this.$primevue.config.locale.aria.expandRow : this.$primevue.config.locale.aria.collapseRow) : undefined;
|
||||||
|
},
|
||||||
|
initButtonAriaLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.editRow : undefined;
|
||||||
|
},
|
||||||
|
saveButtonAriaLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.saveEdit : undefined;
|
||||||
|
},
|
||||||
|
cancelButtonAriaLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.cancelEdit : undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="containerClass">
|
<div :class="containerClass">
|
||||||
<div v-if="display === 'row'" class="p-fluid p-column-filter-element">
|
<div v-if="display === 'row'" class="p-fluid p-column-filter-element" v-bind="filterInputProps">
|
||||||
<component :is="filterElement" :field="field" :filterModel="filters[field]" :filterCallback="filterCallback" />
|
<component :is="filterElement" :field="field" :filterModel="filters[field]" :filterCallback="filterCallback" />
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
@ -8,8 +8,10 @@
|
||||||
ref="icon"
|
ref="icon"
|
||||||
type="button"
|
type="button"
|
||||||
class="p-column-filter-menu-button p-link"
|
class="p-column-filter-menu-button p-link"
|
||||||
|
:aria-label="filterMenuButtonAriaLabel"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
:aria-expanded="overlayVisible"
|
:aria-expanded="overlayVisible"
|
||||||
|
:aria-controls="overlayId"
|
||||||
:class="{ 'p-column-filter-menu-button-open': overlayVisible, 'p-column-filter-menu-button-active': hasFilter() }"
|
:class="{ 'p-column-filter-menu-button-open': overlayVisible, 'p-column-filter-menu-button-active': hasFilter() }"
|
||||||
@click="toggleMenu()"
|
@click="toggleMenu()"
|
||||||
@keydown="onToggleButtonKeyDown($event)"
|
@keydown="onToggleButtonKeyDown($event)"
|
||||||
|
@ -19,7 +21,18 @@
|
||||||
<button v-if="showClearButton && display === 'row'" :class="{ 'p-hidden-space': !hasRowFilter() }" type="button" class="p-column-filter-clear-button p-link" @click="clearFilter()"><span class="pi pi-filter-slash"></span></button>
|
<button v-if="showClearButton && display === 'row'" :class="{ 'p-hidden-space': !hasRowFilter() }" type="button" class="p-column-filter-clear-button p-link" @click="clearFilter()"><span class="pi pi-filter-slash"></span></button>
|
||||||
<Portal>
|
<Portal>
|
||||||
<transition name="p-connected-overlay" @enter="onOverlayEnter" @leave="onOverlayLeave" @after-leave="onOverlayAfterLeave">
|
<transition name="p-connected-overlay" @enter="onOverlayEnter" @leave="onOverlayLeave" @after-leave="onOverlayAfterLeave">
|
||||||
<div v-if="overlayVisible" :ref="overlayRef" :class="overlayClass" @keydown.escape="onEscape" @click="onContentClick" @mousedown="onContentMouseDown">
|
<div
|
||||||
|
v-if="overlayVisible"
|
||||||
|
:ref="overlayRef"
|
||||||
|
:id="overlayId"
|
||||||
|
v-focustrap="{ autoFocus: true }"
|
||||||
|
:aria-modal="overlayVisible"
|
||||||
|
role="dialog"
|
||||||
|
:class="overlayClass"
|
||||||
|
@keydown.escape="hide"
|
||||||
|
@click="onContentClick"
|
||||||
|
@mousedown="onContentMouseDown"
|
||||||
|
>
|
||||||
<component :is="filterHeaderTemplate" :field="field" :filterModel="filters[field]" :filterCallback="filterCallback" />
|
<component :is="filterHeaderTemplate" :field="field" :filterModel="filters[field]" :filterCallback="filterCallback" />
|
||||||
<template v-if="display === 'row'">
|
<template v-if="display === 'row'">
|
||||||
<ul class="p-column-filter-row-items">
|
<ul class="p-column-filter-row-items">
|
||||||
|
@ -41,7 +54,15 @@
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div v-if="isShowOperator" class="p-column-filter-operator">
|
<div v-if="isShowOperator" class="p-column-filter-operator">
|
||||||
<CFDropdown :options="operatorOptions" :modelValue="operator" @update:modelValue="onOperatorChange($event)" class="p-column-filter-operator-dropdown" optionLabel="label" optionValue="value"></CFDropdown>
|
<CFDropdown
|
||||||
|
:options="operatorOptions"
|
||||||
|
:modelValue="operator"
|
||||||
|
:aria-label="filterOperatorAriaLabel"
|
||||||
|
class="p-column-filter-operator-dropdown"
|
||||||
|
optionLabel="label"
|
||||||
|
optionValue="value"
|
||||||
|
@update:modelValue="onOperatorChange($event)"
|
||||||
|
></CFDropdown>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-column-filter-constraints">
|
<div class="p-column-filter-constraints">
|
||||||
<div v-for="(fieldConstraint, i) of fieldConstraints" :key="i" class="p-column-filter-constraint">
|
<div v-for="(fieldConstraint, i) of fieldConstraints" :key="i" class="p-column-filter-constraint">
|
||||||
|
@ -49,10 +70,11 @@
|
||||||
v-if="isShowMatchModes"
|
v-if="isShowMatchModes"
|
||||||
:options="matchModes"
|
:options="matchModes"
|
||||||
:modelValue="fieldConstraint.matchMode"
|
:modelValue="fieldConstraint.matchMode"
|
||||||
|
class="p-column-filter-matchmode-dropdown"
|
||||||
optionLabel="label"
|
optionLabel="label"
|
||||||
optionValue="value"
|
optionValue="value"
|
||||||
|
:aria-label="filterConstraintAriaLabel"
|
||||||
@update:modelValue="onMenuMatchModeChange($event, i)"
|
@update:modelValue="onMenuMatchModeChange($event, i)"
|
||||||
class="p-column-filter-matchmode-dropdown"
|
|
||||||
></CFDropdown>
|
></CFDropdown>
|
||||||
<component v-if="display === 'menu'" :is="filterElement" :field="field" :filterModel="fieldConstraint" :filterCallback="filterCallback" />
|
<component v-if="display === 'menu'" :is="filterElement" :field="field" :filterModel="fieldConstraint" :filterCallback="filterCallback" />
|
||||||
<div>
|
<div>
|
||||||
|
@ -71,10 +93,10 @@
|
||||||
<CFButton type="button" :label="addRuleButtonLabel" icon="pi pi-plus" class="p-column-filter-add-button p-button-text p-button-sm" @click="addConstraint()"></CFButton>
|
<CFButton type="button" :label="addRuleButtonLabel" icon="pi pi-plus" class="p-column-filter-add-button p-button-text p-button-sm" @click="addConstraint()"></CFButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-column-filter-buttonbar">
|
<div class="p-column-filter-buttonbar">
|
||||||
<CFButton v-if="!filterClearTemplate && showClearButton" type="button" class="p-button-outlined p-button-sm" @click="clearFilter()" :label="clearButtonLabel"></CFButton>
|
<CFButton v-if="!filterClearTemplate && showClearButton" type="button" class="p-button-outlined p-button-sm" :label="clearButtonLabel" @click="clearFilter"></CFButton>
|
||||||
<component v-else :is="filterClearTemplate" :field="field" :filterModel="filters[field]" :filterCallback="clearFilter" />
|
<component v-else :is="filterClearTemplate" :field="field" :filterModel="filters[field]" :filterCallback="clearFilter" />
|
||||||
<template v-if="showApplyButton">
|
<template v-if="showApplyButton">
|
||||||
<CFButton v-if="!filterApplyTemplate" type="button" class="p-button-sm" @click="applyFilter()" :label="applyButtonLabel"></CFButton>
|
<CFButton v-if="!filterApplyTemplate" type="button" class="p-button-sm" :label="applyButtonLabel" @click="applyFilter()"></CFButton>
|
||||||
<component v-else :is="filterApplyTemplate" :field="field" :filterModel="filters[field]" :filterCallback="applyFilter" />
|
<component v-else :is="filterApplyTemplate" :field="field" :filterModel="filters[field]" :filterCallback="applyFilter" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -87,12 +109,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { DomHandler, ConnectedOverlayScrollHandler, ZIndexUtils } from 'primevue/utils';
|
|
||||||
import OverlayEventBus from 'primevue/overlayeventbus';
|
|
||||||
import { FilterOperator } from 'primevue/api';
|
import { FilterOperator } from 'primevue/api';
|
||||||
import Dropdown from 'primevue/dropdown';
|
|
||||||
import Button from 'primevue/button';
|
import Button from 'primevue/button';
|
||||||
|
import Dropdown from 'primevue/dropdown';
|
||||||
|
import FocusTrap from 'primevue/focustrap';
|
||||||
|
import OverlayEventBus from 'primevue/overlayeventbus';
|
||||||
import Portal from 'primevue/portal';
|
import Portal from 'primevue/portal';
|
||||||
|
import { ConnectedOverlayScrollHandler, DomHandler, UniqueComponentId, ZIndexUtils } from 'primevue/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ColumnFilter',
|
name: 'ColumnFilter',
|
||||||
|
@ -166,6 +189,10 @@ export default {
|
||||||
filterMenuStyle: {
|
filterMenuStyle: {
|
||||||
type: null,
|
type: null,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
filterInputProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -251,34 +278,16 @@ export default {
|
||||||
this.overlayVisible = !this.overlayVisible;
|
this.overlayVisible = !this.overlayVisible;
|
||||||
},
|
},
|
||||||
onToggleButtonKeyDown(event) {
|
onToggleButtonKeyDown(event) {
|
||||||
switch (event.key) {
|
switch (event.code) {
|
||||||
|
case 'Enter':
|
||||||
|
case 'Space':
|
||||||
|
this.toggleMenu();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
case 'Tab':
|
|
||||||
this.overlayVisible = false;
|
this.overlayVisible = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ArrowDown':
|
|
||||||
if (this.overlayVisible) {
|
|
||||||
let focusable = DomHandler.getFocusableElements(this.overlay);
|
|
||||||
|
|
||||||
if (focusable) {
|
|
||||||
focusable[0].focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
} else if (event.altKey) {
|
|
||||||
this.overlayVisible = true;
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onEscape() {
|
|
||||||
this.overlayVisible = false;
|
|
||||||
|
|
||||||
if (this.$refs.icon) {
|
|
||||||
this.$refs.icon.focus();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRowMatchModeChange(matchMode) {
|
onRowMatchModeChange(matchMode) {
|
||||||
|
@ -293,7 +302,7 @@ export default {
|
||||||
onRowMatchModeKeyDown(event) {
|
onRowMatchModeKeyDown(event) {
|
||||||
let item = event.target;
|
let item = event.target;
|
||||||
|
|
||||||
switch (event.key) {
|
switch (event.code) {
|
||||||
case 'ArrowDown':
|
case 'ArrowDown':
|
||||||
var nextItem = this.findNextItem(item);
|
var nextItem = this.findNextItem(item);
|
||||||
|
|
||||||
|
@ -379,11 +388,13 @@ export default {
|
||||||
findPrevItem(item) {
|
findPrevItem(item) {
|
||||||
let prevItem = item.previousElementSibling;
|
let prevItem = item.previousElementSibling;
|
||||||
|
|
||||||
if (prevItem) DomHandler.hasClass(prevItem, 'p-column-filter-separator') ? this.findPrevItem(prevItem) : prevItem;
|
if (prevItem) return DomHandler.hasClass(prevItem, 'p-column-filter-separator') ? this.findPrevItem(prevItem) : prevItem;
|
||||||
else return item.parentElement.lastElementChild;
|
else return item.parentElement.lastElementChild;
|
||||||
},
|
},
|
||||||
hide() {
|
hide() {
|
||||||
this.overlayVisible = false;
|
this.overlayVisible = false;
|
||||||
|
|
||||||
|
DomHandler.focus(this.$refs.icon);
|
||||||
},
|
},
|
||||||
onContentClick(event) {
|
onContentClick(event) {
|
||||||
this.selfClick = true;
|
this.selfClick = true;
|
||||||
|
@ -516,6 +527,9 @@ export default {
|
||||||
showMenuButton() {
|
showMenuButton() {
|
||||||
return this.showMenu && (this.display === 'row' ? this.type !== 'boolean' : true);
|
return this.showMenu && (this.display === 'row' ? this.type !== 'boolean' : true);
|
||||||
},
|
},
|
||||||
|
overlayId() {
|
||||||
|
return UniqueComponentId();
|
||||||
|
},
|
||||||
matchModes() {
|
matchModes() {
|
||||||
return (
|
return (
|
||||||
this.matchModeOptions ||
|
this.matchModeOptions ||
|
||||||
|
@ -534,7 +548,7 @@ export default {
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
noFilterLabel() {
|
noFilterLabel() {
|
||||||
return this.$primevue.config.locale.noFilter;
|
return this.$primevue.config.locale ? this.$primevue.config.locale.noFilter : undefined;
|
||||||
},
|
},
|
||||||
isShowOperator() {
|
isShowOperator() {
|
||||||
return this.showOperator && this.filters[this.field].operator;
|
return this.showOperator && this.filters[this.field].operator;
|
||||||
|
@ -549,25 +563,37 @@ export default {
|
||||||
return this.fieldConstraints.length > 1;
|
return this.fieldConstraints.length > 1;
|
||||||
},
|
},
|
||||||
removeRuleButtonLabel() {
|
removeRuleButtonLabel() {
|
||||||
return this.$primevue.config.locale.removeRule;
|
return this.$primevue.config.locale ? this.$primevue.config.locale.removeRule : undefined;
|
||||||
},
|
},
|
||||||
addRuleButtonLabel() {
|
addRuleButtonLabel() {
|
||||||
return this.$primevue.config.locale.addRule;
|
return this.$primevue.config.locale ? this.$primevue.config.locale.addRule : undefined;
|
||||||
},
|
},
|
||||||
isShowAddConstraint() {
|
isShowAddConstraint() {
|
||||||
return this.showAddButton && this.filters[this.field].operator && this.fieldConstraints && this.fieldConstraints.length < this.maxConstraints;
|
return this.showAddButton && this.filters[this.field].operator && this.fieldConstraints && this.fieldConstraints.length < this.maxConstraints;
|
||||||
},
|
},
|
||||||
clearButtonLabel() {
|
clearButtonLabel() {
|
||||||
return this.$primevue.config.locale.clear;
|
return this.$primevue.config.locale ? this.$primevue.config.locale.clear : undefined;
|
||||||
},
|
},
|
||||||
applyButtonLabel() {
|
applyButtonLabel() {
|
||||||
return this.$primevue.config.locale.apply;
|
return this.$primevue.config.locale ? this.$primevue.config.locale.apply : undefined;
|
||||||
|
},
|
||||||
|
filterMenuButtonAriaLabel() {
|
||||||
|
return this.$primevue.config.locale ? (this.overlayVisible ? this.$primevue.config.locale.showFilterMenu : this.$primevue.config.locale.hideFilterMenu) : undefined;
|
||||||
|
},
|
||||||
|
filterOperatorAriaLabel() {
|
||||||
|
return this.$primevue.config.locale ? this.$primevue.config.locale.filterOperator : undefined;
|
||||||
|
},
|
||||||
|
filterConstraintAriaLabel() {
|
||||||
|
return this.$primevue.config.locale ? this.$primevue.config.locale.filterConstraint : undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
CFDropdown: Dropdown,
|
CFDropdown: Dropdown,
|
||||||
CFButton: Button,
|
CFButton: Button,
|
||||||
Portal: Portal
|
Portal: Portal
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
focustrap: FocusTrap
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { VNode } from 'vue';
|
import { InputHTMLAttributes, TableHTMLAttributes, VNode } from 'vue';
|
||||||
import { ClassComponent, GlobalComponentConstructor, Nullable } from '../ts-helpers';
|
import { ClassComponent, GlobalComponentConstructor, Nullable } from '../ts-helpers';
|
||||||
import Column from '../column';
|
|
||||||
import { VirtualScrollerProps } from '../virtualscroller';
|
import { VirtualScrollerProps } from '../virtualscroller';
|
||||||
|
|
||||||
type DataTablePaginatorPositionType = 'top' | 'bottom' | 'both' | undefined;
|
type DataTablePaginatorPositionType = 'top' | 'bottom' | 'both' | undefined;
|
||||||
|
@ -511,7 +510,7 @@ export interface DataTableProps {
|
||||||
* - JumpToPageInput
|
* - JumpToPageInput
|
||||||
* - CurrentPageReport
|
* - CurrentPageReport
|
||||||
*/
|
*/
|
||||||
paginatorTemplate?: string | undefined;
|
paginatorTemplate?: any | string;
|
||||||
/**
|
/**
|
||||||
* Number of page links to display.
|
* Number of page links to display.
|
||||||
* Default value is 5.
|
* Default value is 5.
|
||||||
|
@ -685,7 +684,7 @@ export interface DataTableProps {
|
||||||
/**
|
/**
|
||||||
* One or more field names to use in row grouping.
|
* One or more field names to use in row grouping.
|
||||||
*/
|
*/
|
||||||
groupRowsBy?: string[] | string | undefined;
|
groupRowsBy?: (field: string) => object | string[] | string | undefined;
|
||||||
/**
|
/**
|
||||||
* Whether the row groups can be expandable.
|
* Whether the row groups can be expandable.
|
||||||
*/
|
*/
|
||||||
|
@ -776,6 +775,14 @@ export interface DataTableProps {
|
||||||
* Style class of the table element.
|
* Style class of the table element.
|
||||||
*/
|
*/
|
||||||
tableClass?: any;
|
tableClass?: any;
|
||||||
|
/**
|
||||||
|
* Uses to pass all properties of the TableHTMLAttributes to table element inside the component.
|
||||||
|
*/
|
||||||
|
tableProps?: TableHTMLAttributes | undefined;
|
||||||
|
/**
|
||||||
|
* Uses to pass all properties of the HTMLInputElement to the focusable filter input element inside the component.
|
||||||
|
*/
|
||||||
|
filterInputProps?: InputHTMLAttributes | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataTableSlots {
|
export interface DataTableSlots {
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
|
import Button from '@/components/button/Button.vue';
|
||||||
|
import Column from '@/components/column/Column.vue';
|
||||||
|
import ColumnGroup from '@/components/columngroup/ColumnGroup.vue';
|
||||||
|
import InputText from '@/components/inputtext/InputText.vue';
|
||||||
|
import Row from '@/components/row/Row.vue';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import DataTable from './DataTable.vue';
|
|
||||||
import ColumnGroup from '../columngroup/ColumnGroup.vue';
|
|
||||||
import Row from '../row/Row.vue';
|
|
||||||
import Column from '../column/Column.vue';
|
|
||||||
import Button from '../button/Button.vue';
|
|
||||||
import InputText from '../inputtext/InputText.vue';
|
|
||||||
import { FilterMatchMode } from 'primevue/api';
|
import { FilterMatchMode } from 'primevue/api';
|
||||||
|
import PrimeVue from 'primevue/config';
|
||||||
|
import DataTable from './DataTable.vue';
|
||||||
|
|
||||||
window.URL.createObjectURL = function () {};
|
window.URL.createObjectURL = function () {};
|
||||||
|
|
||||||
|
@ -86,6 +87,7 @@ describe('DataTable.vue', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column,
|
Column,
|
||||||
Button
|
Button
|
||||||
|
@ -284,6 +286,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should single sort', async () => {
|
it('should single sort', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -301,7 +304,7 @@ describe('DataTable.vue', () => {
|
||||||
|
|
||||||
const sortableTH = wrapper.findAll('.p-sortable-column')[0];
|
const sortableTH = wrapper.findAll('.p-sortable-column')[0];
|
||||||
const firstCellText = wrapper.findAll('.p-datatable-tbody > tr')[0].findAll('td')[1].text();
|
const firstCellText = wrapper.findAll('.p-datatable-tbody > tr')[0].findAll('td')[1].text();
|
||||||
const headerClick = vi.spyOn(wrapper.vm, 'onColumnHeaderClick');
|
const headerClick = jest.spyOn(wrapper.vm, 'onColumnHeaderClick');
|
||||||
|
|
||||||
await sortableTH.trigger('click');
|
await sortableTH.trigger('click');
|
||||||
|
|
||||||
|
@ -315,6 +318,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should multiple sort', async () => {
|
it('should multiple sort', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -333,7 +337,7 @@ describe('DataTable.vue', () => {
|
||||||
|
|
||||||
const sortableTHs = wrapper.findAll('.p-sortable-column');
|
const sortableTHs = wrapper.findAll('.p-sortable-column');
|
||||||
const firstCellText = wrapper.findAll('.p-datatable-tbody > tr')[0].findAll('td')[1].text();
|
const firstCellText = wrapper.findAll('.p-datatable-tbody > tr')[0].findAll('td')[1].text();
|
||||||
const headerClick = vi.spyOn(wrapper.vm, 'onColumnHeaderClick');
|
const headerClick = jest.spyOn(wrapper.vm, 'onColumnHeaderClick');
|
||||||
|
|
||||||
await sortableTHs[0].trigger('click');
|
await sortableTHs[0].trigger('click');
|
||||||
|
|
||||||
|
@ -355,6 +359,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should have presort', async () => {
|
it('should have presort', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -381,6 +386,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should remove sort', async () => {
|
it('should remove sort', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -509,6 +515,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should select when radiobutton selection is enabled', async () => {
|
it('should select when radiobutton selection is enabled', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -544,6 +551,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should select when checkbox selection is enabled', async () => {
|
it('should select when checkbox selection is enabled', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -577,6 +585,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should select all rows', async () => {
|
it('should select all rows', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -603,6 +612,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should unselect all rows', async () => {
|
it('should unselect all rows', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -663,6 +673,7 @@ describe('DataTable.vue', () => {
|
||||||
wrapper = null;
|
wrapper = null;
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -719,6 +730,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should init row editing', async () => {
|
it('should init row editing', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column,
|
Column,
|
||||||
InputText
|
InputText
|
||||||
|
@ -759,6 +771,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should save row editing', async () => {
|
it('should save row editing', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column,
|
Column,
|
||||||
InputText
|
InputText
|
||||||
|
@ -795,6 +808,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should cancel row editing', async () => {
|
it('should cancel row editing', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column,
|
Column,
|
||||||
InputText
|
InputText
|
||||||
|
@ -832,6 +846,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should fit mode expanding exists', () => {
|
it('should fit mode expanding exists', () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -857,6 +872,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should fit mode resize start', async () => {
|
it('should fit mode resize start', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -885,6 +901,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should fit mode resize', async () => {
|
it('should fit mode resize', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -910,6 +927,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should fit mode column resize end', async () => {
|
it('should fit mode column resize end', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -939,6 +957,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should expand mode resize start', async () => {
|
it('should expand mode resize start', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -967,6 +986,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should fit mode resize', async () => {
|
it('should fit mode resize', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -992,6 +1012,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should fit mode column resize end', async () => {
|
it('should fit mode column resize end', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -1030,6 +1051,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should exist', () => {
|
it('should exist', () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -1054,6 +1076,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should exist', () => {
|
it('should exist', () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -1085,6 +1108,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should have groupheader templating', () => {
|
it('should have groupheader templating', () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -1115,6 +1139,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should have groupfooter templating', () => {
|
it('should have groupfooter templating', () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -1145,6 +1170,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should have expandable row groups and expand rows', async () => {
|
it('should have expandable row groups and expand rows', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -1270,6 +1296,7 @@ describe('DataTable.vue', () => {
|
||||||
it('should have rowspan grouping', async () => {
|
it('should have rowspan grouping', async () => {
|
||||||
wrapper = mount(DataTable, {
|
wrapper = mount(DataTable, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
components: {
|
components: {
|
||||||
Column
|
Column
|
||||||
}
|
}
|
||||||
|
@ -1338,7 +1365,7 @@ describe('DataTable.vue', () => {
|
||||||
|
|
||||||
// export
|
// export
|
||||||
it('should export table', async () => {
|
it('should export table', async () => {
|
||||||
const exportCSV = vi.spyOn(wrapper.vm, 'exportCSV');
|
const exportCSV = jest.spyOn(wrapper.vm, 'exportCSV');
|
||||||
|
|
||||||
await wrapper.vm.exportCSV();
|
await wrapper.vm.exportCSV();
|
||||||
|
|
||||||
|
@ -1355,8 +1382,8 @@ describe('DataTable.vue', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should save session storage', async () => {
|
it('should save session storage', async () => {
|
||||||
vi.spyOn(window.sessionStorage.__proto__, 'setItem');
|
jest.spyOn(window.sessionStorage.__proto__, 'setItem');
|
||||||
window.sessionStorage.__proto__.setItem = vi.fn();
|
window.sessionStorage.__proto__.setItem = jest.fn();
|
||||||
|
|
||||||
await wrapper.vm.saveState();
|
await wrapper.vm.saveState();
|
||||||
|
|
||||||
|
@ -1365,8 +1392,8 @@ describe('DataTable.vue', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should save local storage', async () => {
|
it('should save local storage', async () => {
|
||||||
vi.spyOn(window.localStorage.__proto__, 'setItem');
|
jest.spyOn(window.localStorage.__proto__, 'setItem');
|
||||||
window.localStorage.__proto__.setItem = vi.fn();
|
window.localStorage.__proto__.setItem = jest.fn();
|
||||||
|
|
||||||
await wrapper.vm.saveState();
|
await wrapper.vm.saveState();
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
<div class="p-datatable-wrapper" :style="{ maxHeight: virtualScrollerDisabled ? scrollHeight : '' }">
|
<div class="p-datatable-wrapper" :style="{ maxHeight: virtualScrollerDisabled ? scrollHeight : '' }">
|
||||||
<DTVirtualScroller ref="virtualScroller" v-bind="virtualScrollerOptions" :items="processedData" :columns="columns" :style="{ height: scrollHeight }" :disabled="virtualScrollerDisabled" loaderDisabled :showSpacer="false">
|
<DTVirtualScroller ref="virtualScroller" v-bind="virtualScrollerOptions" :items="processedData" :columns="columns" :style="{ height: scrollHeight }" :disabled="virtualScrollerDisabled" loaderDisabled :showSpacer="false">
|
||||||
<template #content="slotProps">
|
<template #content="slotProps">
|
||||||
<table ref="table" role="table" :class="[tableClass, 'p-datatable-table']" :style="[tableStyle, slotProps.spacerStyle]">
|
<table ref="table" role="table" :class="[tableClass, 'p-datatable-table']" :style="[tableStyle, slotProps.spacerStyle]" v-bind="tableProps">
|
||||||
<DTTableHeader
|
<DTTableHeader
|
||||||
:columnGroup="headerColumnGroup"
|
:columnGroup="headerColumnGroup"
|
||||||
:columns="slotProps.columns"
|
:columns="slotProps.columns"
|
||||||
|
@ -49,6 +49,7 @@
|
||||||
:filters="d_filters"
|
:filters="d_filters"
|
||||||
:filtersStore="filters"
|
:filtersStore="filters"
|
||||||
:filterDisplay="filterDisplay"
|
:filterDisplay="filterDisplay"
|
||||||
|
:filterInputProps="filterInputProps"
|
||||||
@column-click="onColumnHeaderClick($event)"
|
@column-click="onColumnHeaderClick($event)"
|
||||||
@column-mousedown="onColumnHeaderMouseDown($event)"
|
@column-mousedown="onColumnHeaderMouseDown($event)"
|
||||||
@filter-change="onFilterChange"
|
@filter-change="onFilterChange"
|
||||||
|
@ -90,6 +91,7 @@
|
||||||
:editingRowKeys="d_editingRowKeys"
|
:editingRowKeys="d_editingRowKeys"
|
||||||
:templates="$slots"
|
:templates="$slots"
|
||||||
:responsiveLayout="responsiveLayout"
|
:responsiveLayout="responsiveLayout"
|
||||||
|
:isVirtualScrollerDisabled="true"
|
||||||
@rowgroup-toggle="toggleRowGroup"
|
@rowgroup-toggle="toggleRowGroup"
|
||||||
@row-click="onRowClick($event)"
|
@row-click="onRowClick($event)"
|
||||||
@row-dblclick="onRowDblClick($event)"
|
@row-dblclick="onRowDblClick($event)"
|
||||||
|
@ -113,7 +115,6 @@
|
||||||
@row-edit-cancel="onRowEditCancel($event)"
|
@row-edit-cancel="onRowEditCancel($event)"
|
||||||
:editingMeta="d_editingMeta"
|
:editingMeta="d_editingMeta"
|
||||||
@editing-meta-change="onEditingMetaChange"
|
@editing-meta-change="onEditingMetaChange"
|
||||||
:isVirtualScrollerDisabled="true"
|
|
||||||
/>
|
/>
|
||||||
<DTTableBody
|
<DTTableBody
|
||||||
ref="bodyRef"
|
ref="bodyRef"
|
||||||
|
@ -144,12 +145,14 @@
|
||||||
:editingRowKeys="d_editingRowKeys"
|
:editingRowKeys="d_editingRowKeys"
|
||||||
:templates="$slots"
|
:templates="$slots"
|
||||||
:responsiveLayout="responsiveLayout"
|
:responsiveLayout="responsiveLayout"
|
||||||
|
:virtualScrollerContentProps="slotProps"
|
||||||
|
:isVirtualScrollerDisabled="virtualScrollerDisabled"
|
||||||
@rowgroup-toggle="toggleRowGroup"
|
@rowgroup-toggle="toggleRowGroup"
|
||||||
@row-click="onRowClick($event)"
|
@row-click="onRowClick($event)"
|
||||||
@row-dblclick="onRowDblClick($event)"
|
@row-dblclick="onRowDblClick($event)"
|
||||||
@row-rightclick="onRowRightClick($event)"
|
@row-rightclick="onRowRightClick($event)"
|
||||||
@row-touchend="onRowTouchEnd"
|
@row-touchend="onRowTouchEnd"
|
||||||
@row-keydown="onRowKeyDown"
|
@row-keydown="onRowKeyDown($event, slotProps)"
|
||||||
@row-mousedown="onRowMouseDown"
|
@row-mousedown="onRowMouseDown"
|
||||||
@row-dragstart="onRowDragStart($event)"
|
@row-dragstart="onRowDragStart($event)"
|
||||||
@row-dragover="onRowDragOver($event)"
|
@row-dragover="onRowDragOver($event)"
|
||||||
|
@ -167,8 +170,6 @@
|
||||||
@row-edit-cancel="onRowEditCancel($event)"
|
@row-edit-cancel="onRowEditCancel($event)"
|
||||||
:editingMeta="d_editingMeta"
|
:editingMeta="d_editingMeta"
|
||||||
@editing-meta-change="onEditingMetaChange"
|
@editing-meta-change="onEditingMetaChange"
|
||||||
:virtualScrollerContentProps="slotProps"
|
|
||||||
:isVirtualScrollerDisabled="virtualScrollerDisabled"
|
|
||||||
/>
|
/>
|
||||||
<DTTableFooter :columnGroup="footerColumnGroup" :columns="slotProps.columns" />
|
<DTTableFooter :columnGroup="footerColumnGroup" :columns="slotProps.columns" />
|
||||||
</table>
|
</table>
|
||||||
|
@ -205,13 +206,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ObjectUtils, DomHandler, UniqueComponentId } from 'primevue/utils';
|
|
||||||
import { FilterMatchMode, FilterOperator, FilterService } from 'primevue/api';
|
import { FilterMatchMode, FilterOperator, FilterService } from 'primevue/api';
|
||||||
import Paginator from 'primevue/paginator';
|
import Paginator from 'primevue/paginator';
|
||||||
|
import { DomHandler, ObjectUtils, UniqueComponentId } from 'primevue/utils';
|
||||||
import VirtualScroller from 'primevue/virtualscroller';
|
import VirtualScroller from 'primevue/virtualscroller';
|
||||||
import TableHeader from './TableHeader.vue';
|
|
||||||
import TableBody from './TableBody.vue';
|
import TableBody from './TableBody.vue';
|
||||||
import TableFooter from './TableFooter.vue';
|
import TableFooter from './TableFooter.vue';
|
||||||
|
import TableHeader from './TableHeader.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DataTable',
|
name: 'DataTable',
|
||||||
|
@ -289,7 +290,7 @@ export default {
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
paginatorTemplate: {
|
paginatorTemplate: {
|
||||||
type: String,
|
type: [Object, String],
|
||||||
default: 'FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown'
|
default: 'FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown'
|
||||||
},
|
},
|
||||||
pageLinkSize: {
|
pageLinkSize: {
|
||||||
|
@ -433,7 +434,7 @@ export default {
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
groupRowsBy: {
|
groupRowsBy: {
|
||||||
type: [Array, String],
|
type: [Array, String, Function],
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
expandableRowGroups: {
|
expandableRowGroups: {
|
||||||
|
@ -511,6 +512,14 @@ export default {
|
||||||
tableClass: {
|
tableClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
tableProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
filterInputProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -864,6 +873,9 @@ export default {
|
||||||
},
|
},
|
||||||
onRowClick(e) {
|
onRowClick(e) {
|
||||||
const event = e.originalEvent;
|
const event = e.originalEvent;
|
||||||
|
const index = e.index;
|
||||||
|
const body = this.$refs.bodyRef && this.$refs.bodyRef.$el;
|
||||||
|
const focusedItem = DomHandler.findSingle(body, 'tr.p-selectable-row[tabindex="0"]');
|
||||||
|
|
||||||
if (DomHandler.isClickable(event.target)) {
|
if (DomHandler.isClickable(event.target)) {
|
||||||
return;
|
return;
|
||||||
|
@ -940,6 +952,11 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.rowTouched = false;
|
this.rowTouched = false;
|
||||||
|
|
||||||
|
if (focusedItem) {
|
||||||
|
focusedItem.tabIndex = '-1';
|
||||||
|
DomHandler.find(body, 'tr.p-selectable-row')[index].tabIndex = '0';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onRowDblClick(e) {
|
onRowDblClick(e) {
|
||||||
const event = e.originalEvent;
|
const event = e.originalEvent;
|
||||||
|
@ -960,48 +977,153 @@ export default {
|
||||||
onRowTouchEnd() {
|
onRowTouchEnd() {
|
||||||
this.rowTouched = true;
|
this.rowTouched = true;
|
||||||
},
|
},
|
||||||
onRowKeyDown(e) {
|
onRowKeyDown(e, slotProps) {
|
||||||
const event = e.originalEvent;
|
const event = e.originalEvent;
|
||||||
const rowData = e.data;
|
const rowData = e.data;
|
||||||
const rowIndex = e.index;
|
const rowIndex = e.index;
|
||||||
|
const metaKey = event.metaKey || event.ctrlKey;
|
||||||
|
|
||||||
if (this.selectionMode) {
|
if (this.selectionMode) {
|
||||||
const row = event.target;
|
const row = event.target;
|
||||||
|
|
||||||
switch (event.which) {
|
switch (event.code) {
|
||||||
//down arrow
|
case 'ArrowDown':
|
||||||
case 40:
|
this.onArrowDownKey(event, row, rowIndex, slotProps);
|
||||||
var nextRow = this.findNextSelectableRow(row);
|
|
||||||
|
|
||||||
if (nextRow) {
|
|
||||||
nextRow.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
//up arrow
|
case 'ArrowUp':
|
||||||
case 38:
|
this.onArrowUpKey(event, row, rowIndex, slotProps);
|
||||||
var prevRow = this.findPrevSelectableRow(row);
|
|
||||||
|
|
||||||
if (prevRow) {
|
|
||||||
prevRow.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
//enter
|
case 'Home':
|
||||||
case 13:
|
this.onHomeKey(event, row, rowIndex, slotProps);
|
||||||
this.onRowClick({ originalEvent: event, data: rowData, index: rowIndex });
|
break;
|
||||||
|
|
||||||
|
case 'End':
|
||||||
|
this.onEndKey(event, row, rowIndex, slotProps);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Enter':
|
||||||
|
this.onEnterKey(event, rowData, rowIndex);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Space':
|
||||||
|
this.onSpaceKey(event, rowData, rowIndex, slotProps);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Tab':
|
||||||
|
this.onTabKey(event, rowIndex);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
//no op
|
if (event.code === 'KeyA' && metaKey) {
|
||||||
|
const data = this.dataToRender(slotProps.rows);
|
||||||
|
|
||||||
|
this.$emit('update:selection', data);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onArrowDownKey(event, row, rowIndex, slotProps) {
|
||||||
|
const nextRow = this.findNextSelectableRow(row);
|
||||||
|
|
||||||
|
nextRow && this.focusRowChange(row, nextRow);
|
||||||
|
|
||||||
|
if (event.shiftKey) {
|
||||||
|
const data = this.dataToRender(slotProps.rows);
|
||||||
|
const nextRowIndex = rowIndex + 1 >= data.length ? data.length - 1 : rowIndex + 1;
|
||||||
|
|
||||||
|
this.onRowClick({ originalEvent: event, data: data[nextRowIndex], index: nextRowIndex });
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
onArrowUpKey(event, row, rowIndex, slotProps) {
|
||||||
|
const prevRow = this.findPrevSelectableRow(row);
|
||||||
|
|
||||||
|
prevRow && this.focusRowChange(row, prevRow);
|
||||||
|
|
||||||
|
if (event.shiftKey) {
|
||||||
|
const data = this.dataToRender(slotProps.rows);
|
||||||
|
const prevRowIndex = rowIndex - 1 <= 0 ? 0 : rowIndex - 1;
|
||||||
|
|
||||||
|
this.onRowClick({ originalEvent: event, data: data[prevRowIndex], index: prevRowIndex });
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
onHomeKey(event, row, rowIndex, slotProps) {
|
||||||
|
const firstRow = this.findFirstSelectableRow();
|
||||||
|
|
||||||
|
firstRow && this.focusRowChange(row, firstRow);
|
||||||
|
|
||||||
|
if (event.ctrlKey && event.shiftKey) {
|
||||||
|
const data = this.dataToRender(slotProps.rows);
|
||||||
|
|
||||||
|
this.$emit('update:selection', data.slice(0, rowIndex + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
onEndKey(event, row, rowIndex, slotProps) {
|
||||||
|
const lastRow = this.findLastSelectableRow();
|
||||||
|
|
||||||
|
lastRow && this.focusRowChange(row, lastRow);
|
||||||
|
|
||||||
|
if (event.ctrlKey && event.shiftKey) {
|
||||||
|
const data = this.dataToRender(slotProps.rows);
|
||||||
|
|
||||||
|
this.$emit('update:selection', data.slice(rowIndex, data.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
onEnterKey(event, rowData, rowIndex) {
|
||||||
|
this.onRowClick({ originalEvent: event, data: rowData, index: rowIndex });
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
onSpaceKey(event, rowData, rowIndex, slotProps) {
|
||||||
|
this.onEnterKey(event, rowData, rowIndex);
|
||||||
|
|
||||||
|
if (event.shiftKey && this.selection !== null) {
|
||||||
|
const data = this.dataToRender(slotProps.rows);
|
||||||
|
let index;
|
||||||
|
|
||||||
|
if (this.selection.length > 0) {
|
||||||
|
let firstSelectedRowIndex, lastSelectedRowIndex;
|
||||||
|
|
||||||
|
firstSelectedRowIndex = ObjectUtils.findIndexInList(this.selection[0], data);
|
||||||
|
lastSelectedRowIndex = ObjectUtils.findIndexInList(this.selection[this.selection.length - 1], data);
|
||||||
|
|
||||||
|
index = rowIndex <= firstSelectedRowIndex ? lastSelectedRowIndex : firstSelectedRowIndex;
|
||||||
|
} else {
|
||||||
|
index = ObjectUtils.findIndexInList(this.selection, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _selection = index !== rowIndex ? data.slice(Math.min(index, rowIndex), Math.max(index, rowIndex) + 1) : rowData;
|
||||||
|
|
||||||
|
this.$emit('update:selection', _selection);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTabKey(event, rowIndex) {
|
||||||
|
const body = this.$refs.bodyRef && this.$refs.bodyRef.$el;
|
||||||
|
const rows = DomHandler.find(body, 'tr.p-selectable-row');
|
||||||
|
|
||||||
|
if (event.code === 'Tab' && rows && rows.length > 0) {
|
||||||
|
const firstSelectedRow = DomHandler.findSingle(body, 'tr.p-highlight');
|
||||||
|
const focusedItem = DomHandler.findSingle(body, 'tr.p-selectable-row[tabindex="0"]');
|
||||||
|
|
||||||
|
if (firstSelectedRow) {
|
||||||
|
firstSelectedRow.tabIndex = '0';
|
||||||
|
focusedItem !== firstSelectedRow && (focusedItem.tabIndex = '-1');
|
||||||
|
} else {
|
||||||
|
rows[0].tabIndex = '0';
|
||||||
|
focusedItem !== rows[0] && (rows[rowIndex].tabIndex = '-1');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
findNextSelectableRow(row) {
|
findNextSelectableRow(row) {
|
||||||
let nextRow = row.nextElementSibling;
|
let nextRow = row.nextElementSibling;
|
||||||
|
|
||||||
|
@ -1022,6 +1144,21 @@ export default {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
findFirstSelectableRow() {
|
||||||
|
const firstRow = DomHandler.findSingle(this.$refs.table, '.p-selectable-row');
|
||||||
|
|
||||||
|
return firstRow;
|
||||||
|
},
|
||||||
|
findLastSelectableRow() {
|
||||||
|
const rows = DomHandler.find(this.$refs.table, '.p-selectable-row');
|
||||||
|
|
||||||
|
return rows ? rows[rows.length - 1] : null;
|
||||||
|
},
|
||||||
|
focusRowChange(firstFocusableRow, currentFocusedRow) {
|
||||||
|
firstFocusableRow.tabIndex = '-1';
|
||||||
|
currentFocusedRow.tabIndex = '0';
|
||||||
|
DomHandler.focus(currentFocusedRow);
|
||||||
|
},
|
||||||
toggleRowWithRadio(event) {
|
toggleRowWithRadio(event) {
|
||||||
const rowData = event.data;
|
const rowData = event.data;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,10 @@
|
||||||
:style="containerStyle"
|
:style="containerStyle"
|
||||||
:class="containerClass"
|
:class="containerClass"
|
||||||
:tabindex="columnProp('sortable') ? '0' : null"
|
:tabindex="columnProp('sortable') ? '0' : null"
|
||||||
role="cell"
|
role="columnheader"
|
||||||
|
:colspan="columnProp('colspan')"
|
||||||
|
:rowspan="columnProp('rowspan')"
|
||||||
|
:aria-sort="ariaSort"
|
||||||
@click="onClick"
|
@click="onClick"
|
||||||
@keydown="onKeyDown"
|
@keydown="onKeyDown"
|
||||||
@mousedown="onMouseDown"
|
@mousedown="onMouseDown"
|
||||||
|
@ -11,9 +14,6 @@
|
||||||
@dragover="onDragOver"
|
@dragover="onDragOver"
|
||||||
@dragleave="onDragLeave"
|
@dragleave="onDragLeave"
|
||||||
@drop="onDrop"
|
@drop="onDrop"
|
||||||
:colspan="columnProp('colspan')"
|
|
||||||
:rowspan="columnProp('rowspan')"
|
|
||||||
:aria-sort="ariaSort"
|
|
||||||
>
|
>
|
||||||
<span v-if="resizableColumns && !columnProp('frozen')" class="p-column-resizer" @mousedown="onResizeStart"></span>
|
<span v-if="resizableColumns && !columnProp('frozen')" class="p-column-resizer" @mousedown="onResizeStart"></span>
|
||||||
<div class="p-column-header-content">
|
<div class="p-column-header-content">
|
||||||
|
@ -35,6 +35,7 @@
|
||||||
:filterApplyTemplate="column.children && column.children.filterapply"
|
:filterApplyTemplate="column.children && column.children.filterapply"
|
||||||
:filters="filters"
|
:filters="filters"
|
||||||
:filtersStore="filtersStore"
|
:filtersStore="filtersStore"
|
||||||
|
:filterInputProps="filterInputProps"
|
||||||
@filter-change="$emit('filter-change', $event)"
|
@filter-change="$emit('filter-change', $event)"
|
||||||
@filter-apply="$emit('filter-apply')"
|
@filter-apply="$emit('filter-apply')"
|
||||||
:filterMenuStyle="columnProp('filterMenuStyle')"
|
:filterMenuStyle="columnProp('filterMenuStyle')"
|
||||||
|
@ -58,8 +59,8 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { DomHandler, ObjectUtils } from 'primevue/utils';
|
import { DomHandler, ObjectUtils } from 'primevue/utils';
|
||||||
import HeaderCheckbox from './HeaderCheckbox.vue';
|
|
||||||
import ColumnFilter from './ColumnFilter.vue';
|
import ColumnFilter from './ColumnFilter.vue';
|
||||||
|
import HeaderCheckbox from './HeaderCheckbox.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HeaderCell',
|
name: 'HeaderCell',
|
||||||
|
@ -91,7 +92,7 @@ export default {
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
groupRowsBy: {
|
groupRowsBy: {
|
||||||
type: [Array, String],
|
type: [Array, String, Function],
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
sortMode: {
|
sortMode: {
|
||||||
|
@ -141,6 +142,10 @@ export default {
|
||||||
reorderableColumns: {
|
reorderableColumns: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
filterInputProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -166,8 +171,9 @@ export default {
|
||||||
this.$emit('column-click', { originalEvent: event, column: this.column });
|
this.$emit('column-click', { originalEvent: event, column: this.column });
|
||||||
},
|
},
|
||||||
onKeyDown(event) {
|
onKeyDown(event) {
|
||||||
if (event.which === 13 && event.currentTarget.nodeName === 'TH' && DomHandler.hasClass(event.currentTarget, 'p-sortable-column')) {
|
if ((event.code === 'Enter' || event.code === 'Space') && event.currentTarget.nodeName === 'TH' && DomHandler.hasClass(event.currentTarget, 'p-sortable-column')) {
|
||||||
this.$emit('column-click', { originalEvent: event, column: this.column });
|
this.$emit('column-click', { originalEvent: event, column: this.column });
|
||||||
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onMouseDown(event) {
|
onMouseDown(event) {
|
||||||
|
|
|
@ -1,26 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="['p-checkbox p-component', { 'p-checkbox-focused': focused, 'p-disabled': $attrs.disabled }]" @click="onClick" @keydown.space.prevent="onClick">
|
<div :class="['p-checkbox p-component', { 'p-checkbox-focused': focused, 'p-disabled': disabled }]" @click="onClick" @keydown.space.prevent="onClick">
|
||||||
<div
|
<div class="p-hidden-accessible">
|
||||||
ref="box"
|
<input ref="input" type="checkbox" :checked="checked" :disabled="disabled" :tabindex="disabled ? null : '0'" :aria-label="headerCheckboxAriaLabel" @focus="onFocus($event)" @blur="onBlur($event)" />
|
||||||
:class="['p-checkbox-box p-component', { 'p-highlight': checked, 'p-disabled': $attrs.disabled, 'p-focus': focused }]"
|
</div>
|
||||||
role="checkbox"
|
<div ref="box" :class="['p-checkbox-box p-component', { 'p-highlight': checked, 'p-disabled': disabled, 'p-focus': focused }]">
|
||||||
:aria-checked="checked"
|
|
||||||
:tabindex="$attrs.disabled ? null : '0'"
|
|
||||||
@focus="onFocus($event)"
|
|
||||||
@blur="onBlur($event)"
|
|
||||||
>
|
|
||||||
<span :class="['p-checkbox-icon', { 'pi pi-check': checked }]"></span>
|
<span :class="['p-checkbox-icon', { 'pi pi-check': checked }]"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { DomHandler } from 'primevue/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HeaderCheckbox',
|
name: 'HeaderCheckbox',
|
||||||
inheritAttrs: false,
|
|
||||||
emits: ['change'],
|
emits: ['change'],
|
||||||
props: {
|
props: {
|
||||||
checked: null
|
checked: null,
|
||||||
|
disabled: null
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -29,12 +26,13 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onClick(event) {
|
onClick(event) {
|
||||||
if (!this.$attrs.disabled) {
|
if (!this.disabled) {
|
||||||
this.focused = true;
|
|
||||||
this.$emit('change', {
|
this.$emit('change', {
|
||||||
originalEvent: event,
|
originalEvent: event,
|
||||||
checked: !this.checked
|
checked: !this.checked
|
||||||
});
|
});
|
||||||
|
|
||||||
|
DomHandler.focus(this.$refs.input);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onFocus() {
|
onFocus() {
|
||||||
|
@ -43,6 +41,11 @@ export default {
|
||||||
onBlur() {
|
onBlur() {
|
||||||
this.focused = false;
|
this.focused = false;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
headerCheckboxAriaLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? (this.checked ? this.$primevue.config.locale.aria.selectAll : this.$primevue.config.locale.aria.unselectAll) : undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,24 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="['p-checkbox p-component', { 'p-checkbox-focused': focused }]" @click.stop.prevent="onClick">
|
<div :class="['p-checkbox p-component', { 'p-checkbox-focused': focused }]" @click="onClick">
|
||||||
<div
|
<div class="p-hidden-accessible">
|
||||||
ref="box"
|
<input ref="input" type="checkbox" :checked="checked" :disabled="$attrs.disabled" :tabindex="$attrs.disabled ? null : '0'" :aria-label="checkboxAriaLabel" @focus="onFocus($event)" @blur="onBlur($event)" @keydown="onKeydown" />
|
||||||
:class="['p-checkbox-box p-component', { 'p-highlight': checked, 'p-disabled': $attrs.disabled, 'p-focus': focused }]"
|
</div>
|
||||||
role="checkbox"
|
<div ref="box" :class="['p-checkbox-box p-component', { 'p-highlight': checked, 'p-disabled': $attrs.disabled, 'p-focus': focused }]">
|
||||||
:aria-checked="checked"
|
|
||||||
:tabindex="$attrs.disabled ? null : '0'"
|
|
||||||
@keydown.space.prevent="onClick"
|
|
||||||
@focus="onFocus($event)"
|
|
||||||
@blur="onBlur($event)"
|
|
||||||
>
|
|
||||||
<span :class="['p-checkbox-icon', { 'pi pi-check': checked }]"></span>
|
<span :class="['p-checkbox-icon', { 'pi pi-check': checked }]"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { DomHandler } from 'primevue/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'RowCheckbox',
|
name: 'RowCheckbox',
|
||||||
inheritAttrs: false,
|
|
||||||
emits: ['change'],
|
emits: ['change'],
|
||||||
props: {
|
props: {
|
||||||
value: null,
|
value: null,
|
||||||
|
@ -32,18 +27,38 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
onClick(event) {
|
onClick(event) {
|
||||||
if (!this.$attrs.disabled) {
|
if (!this.$attrs.disabled) {
|
||||||
this.focused = true;
|
|
||||||
this.$emit('change', {
|
this.$emit('change', {
|
||||||
originalEvent: event,
|
originalEvent: event,
|
||||||
data: this.value
|
data: this.value
|
||||||
});
|
});
|
||||||
|
|
||||||
|
DomHandler.focus(this.$refs.input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
onFocus() {
|
onFocus() {
|
||||||
this.focused = true;
|
this.focused = true;
|
||||||
},
|
},
|
||||||
onBlur() {
|
onBlur() {
|
||||||
this.focused = false;
|
this.focused = false;
|
||||||
|
},
|
||||||
|
onKeydown(event) {
|
||||||
|
switch (event.code) {
|
||||||
|
case 'Space': {
|
||||||
|
this.onClick(event);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
checkboxAriaLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? (this.checked ? this.$primevue.config.locale.aria.selectRow : this.$primevue.config.locale.aria.unselectRow) : undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,19 +1,25 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="['p-radiobutton p-component', { 'p-radiobutton-focused': focused }]" @click="onClick" tabindex="0" @focus="onFocus($event)" @blur="onBlur($event)" @keydown.space.prevent="onClick">
|
<div :class="['p-radiobutton p-component', { 'p-radiobutton-focused': focused }]" @click="onClick">
|
||||||
<div ref="box" :class="['p-radiobutton-box p-component', { 'p-highlight': checked, 'p-disabled': $attrs.disabled, 'p-focus': focused }]" role="radio" :aria-checked="checked">
|
<div class="p-hidden-accessible">
|
||||||
|
<input ref="input" type="radio" :checked="checked" :disabled="$attrs.disabled" :name="name" tabindex="0" @focus="onFocus($event)" @blur="onBlur($event)" @keydown.space.prevent="onClick" />
|
||||||
|
</div>
|
||||||
|
<div ref="box" :class="['p-radiobutton-box p-component', { 'p-highlight': checked, 'p-disabled': $attrs.disabled, 'p-focus': focused }]">
|
||||||
<div class="p-radiobutton-icon"></div>
|
<div class="p-radiobutton-icon"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { DomHandler } from 'primevue/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'RowRadioButton',
|
name: 'RowRadioButton',
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
emits: ['change'],
|
emits: ['change'],
|
||||||
props: {
|
props: {
|
||||||
value: null,
|
value: null,
|
||||||
checked: null
|
checked: null,
|
||||||
|
name: null
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -28,6 +34,8 @@ export default {
|
||||||
originalEvent: event,
|
originalEvent: event,
|
||||||
data: this.value
|
data: this.value
|
||||||
});
|
});
|
||||||
|
|
||||||
|
DomHandler.focus(this.$refs.input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,19 +15,20 @@
|
||||||
:key="getRowKey(rowData, getRowIndex(index))"
|
:key="getRowKey(rowData, getRowIndex(index))"
|
||||||
:class="getRowClass(rowData)"
|
:class="getRowClass(rowData)"
|
||||||
:style="rowStyle"
|
:style="rowStyle"
|
||||||
|
:tabindex="setRowTabindex(index)"
|
||||||
|
role="row"
|
||||||
|
:aria-selected="selectionMode ? isSelected(rowData) : null"
|
||||||
@click="onRowClick($event, rowData, getRowIndex(index))"
|
@click="onRowClick($event, rowData, getRowIndex(index))"
|
||||||
@dblclick="onRowDblClick($event, rowData, getRowIndex(index))"
|
@dblclick="onRowDblClick($event, rowData, getRowIndex(index))"
|
||||||
@contextmenu="onRowRightClick($event, rowData, getRowIndex(index))"
|
@contextmenu="onRowRightClick($event, rowData, getRowIndex(index))"
|
||||||
@touchend="onRowTouchEnd($event)"
|
@touchend="onRowTouchEnd($event)"
|
||||||
@keydown="onRowKeyDown($event, rowData, getRowIndex(index))"
|
@keydown="onRowKeyDown($event, rowData, getRowIndex(index))"
|
||||||
:tabindex="selectionMode || contextMenu ? '0' : null"
|
|
||||||
@mousedown="onRowMouseDown($event)"
|
@mousedown="onRowMouseDown($event)"
|
||||||
@dragstart="onRowDragStart($event, getRowIndex(index))"
|
@dragstart="onRowDragStart($event, getRowIndex(index))"
|
||||||
@dragover="onRowDragOver($event, getRowIndex(index))"
|
@dragover="onRowDragOver($event, getRowIndex(index))"
|
||||||
@dragleave="onRowDragLeave($event)"
|
@dragleave="onRowDragLeave($event)"
|
||||||
@dragend="onRowDragEnd($event)"
|
@dragend="onRowDragEnd($event)"
|
||||||
@drop="onRowDrop($event)"
|
@drop="onRowDrop($event)"
|
||||||
role="row"
|
|
||||||
>
|
>
|
||||||
<template v-for="(col, i) of columns" :key="columnProp(col, 'columnKey') || columnProp(col, 'field') || i">
|
<template v-for="(col, i) of columns" :key="columnProp(col, 'columnKey') || columnProp(col, 'field') || i">
|
||||||
<DTBodyCell
|
<DTBodyCell
|
||||||
|
@ -42,7 +43,11 @@
|
||||||
:rowspan="rowGroupMode === 'rowspan' ? calculateRowGroupSize(value, col, getRowIndex(index)) : null"
|
:rowspan="rowGroupMode === 'rowspan' ? calculateRowGroupSize(value, col, getRowIndex(index)) : null"
|
||||||
:editMode="editMode"
|
:editMode="editMode"
|
||||||
:editing="editMode === 'row' && isRowEditing(rowData)"
|
:editing="editMode === 'row' && isRowEditing(rowData)"
|
||||||
|
:editingMeta="editingMeta"
|
||||||
:responsiveLayout="responsiveLayout"
|
:responsiveLayout="responsiveLayout"
|
||||||
|
:virtualScrollerContentProps="virtualScrollerContentProps"
|
||||||
|
:ariaControls="expandedRowId + '_' + index + '_expansion'"
|
||||||
|
:name="nameAttributeSelector"
|
||||||
@radio-change="onRadioChange($event)"
|
@radio-change="onRadioChange($event)"
|
||||||
@checkbox-change="onCheckboxChange($event)"
|
@checkbox-change="onCheckboxChange($event)"
|
||||||
@row-toggle="onRowToggle($event)"
|
@row-toggle="onRowToggle($event)"
|
||||||
|
@ -52,13 +57,11 @@
|
||||||
@row-edit-init="onRowEditInit($event)"
|
@row-edit-init="onRowEditInit($event)"
|
||||||
@row-edit-save="onRowEditSave($event)"
|
@row-edit-save="onRowEditSave($event)"
|
||||||
@row-edit-cancel="onRowEditCancel($event)"
|
@row-edit-cancel="onRowEditCancel($event)"
|
||||||
:editingMeta="editingMeta"
|
|
||||||
@editing-meta-change="onEditingMetaChange"
|
@editing-meta-change="onEditingMetaChange"
|
||||||
:virtualScrollerContentProps="virtualScrollerContentProps"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="templates['expansion'] && expandedRows && isRowExpanded(rowData)" :key="getRowKey(rowData, getRowIndex(index)) + '_expansion'" class="p-datatable-row-expansion" role="row">
|
<tr v-if="templates['expansion'] && expandedRows && isRowExpanded(rowData)" :key="getRowKey(rowData, getRowIndex(index)) + '_expansion'" :id="expandedRowId + '_' + index + '_expansion'" class="p-datatable-row-expansion" role="row">
|
||||||
<td :colspan="columnsLength">
|
<td :colspan="columnsLength">
|
||||||
<component :is="templates['expansion']" :data="rowData" :index="getRowIndex(index)" />
|
<component :is="templates['expansion']" :data="rowData" :index="getRowIndex(index)" />
|
||||||
</td>
|
</td>
|
||||||
|
@ -77,7 +80,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ObjectUtils, DomHandler } from 'primevue/utils';
|
import { DomHandler, ObjectUtils, UniqueComponentId } from 'primevue/utils';
|
||||||
import BodyCell from './BodyCell.vue';
|
import BodyCell from './BodyCell.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -128,7 +131,7 @@ export default {
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
groupRowsBy: {
|
groupRowsBy: {
|
||||||
type: [Array, String],
|
type: [Array, String, Function],
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
expandableRowGroups: {
|
expandableRowGroups: {
|
||||||
|
@ -230,7 +233,9 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
rowGroupHeaderStyleObject: {}
|
rowGroupHeaderStyleObject: {},
|
||||||
|
tabindexArray: [],
|
||||||
|
isARowSelected: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -548,6 +553,13 @@ export default {
|
||||||
const contentRef = this.getVirtualScrollerProp('contentRef');
|
const contentRef = this.getVirtualScrollerProp('contentRef');
|
||||||
|
|
||||||
contentRef && contentRef(el);
|
contentRef && contentRef(el);
|
||||||
|
},
|
||||||
|
setRowTabindex(index) {
|
||||||
|
if (this.selection === null && (this.selectionMode === 'single' || this.selectionMode === 'multiple')) {
|
||||||
|
return index === 0 ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -569,6 +581,12 @@ export default {
|
||||||
},
|
},
|
||||||
bodyStyle() {
|
bodyStyle() {
|
||||||
return this.getVirtualScrollerProp('contentStyle');
|
return this.getVirtualScrollerProp('contentStyle');
|
||||||
|
},
|
||||||
|
expandedRowId() {
|
||||||
|
return UniqueComponentId();
|
||||||
|
},
|
||||||
|
nameAttributeSelector() {
|
||||||
|
return UniqueComponentId();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import FooterCell from './FooterCell.vue';
|
|
||||||
import { ObjectUtils } from 'primevue/utils';
|
import { ObjectUtils } from 'primevue/utils';
|
||||||
|
import FooterCell from './FooterCell.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TableFooter',
|
name: 'TableFooter',
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
:filters="filters"
|
:filters="filters"
|
||||||
:filterDisplay="filterDisplay"
|
:filterDisplay="filterDisplay"
|
||||||
:filtersStore="filtersStore"
|
:filtersStore="filtersStore"
|
||||||
|
:filterInputProps="filterInputProps"
|
||||||
@filter-change="$emit('filter-change', $event)"
|
@filter-change="$emit('filter-change', $event)"
|
||||||
@filter-apply="$emit('filter-apply')"
|
@filter-apply="$emit('filter-apply')"
|
||||||
@operator-change="$emit('operator-change', $event)"
|
@operator-change="$emit('operator-change', $event)"
|
||||||
|
@ -40,7 +41,7 @@
|
||||||
<tr v-if="filterDisplay === 'row'" role="row">
|
<tr v-if="filterDisplay === 'row'" role="row">
|
||||||
<template v-for="(col, i) of columns" :key="columnProp(col, 'columnKey') || columnProp(col, 'field') || i">
|
<template v-for="(col, i) of columns" :key="columnProp(col, 'columnKey') || columnProp(col, 'field') || i">
|
||||||
<th v-if="!columnProp(col, 'hidden') && (rowGroupMode !== 'subheader' || groupRowsBy !== columnProp(col, 'field'))" :style="getFilterColumnHeaderStyle(col)" :class="getFilterColumnHeaderClass(col)">
|
<th v-if="!columnProp(col, 'hidden') && (rowGroupMode !== 'subheader' || groupRowsBy !== columnProp(col, 'field'))" :style="getFilterColumnHeaderStyle(col)" :class="getFilterColumnHeaderClass(col)">
|
||||||
<DTHeaderCheckbox v-if="columnProp(col, 'selectionMode') === 'multiple'" :checked="allRowsSelected" @change="$emit('checkbox-change', $event)" :disabled="empty" />
|
<DTHeaderCheckbox v-if="columnProp(col, 'selectionMode') === 'multiple'" :checked="allRowsSelected" :disabled="empty" @change="$emit('checkbox-change', $event)" />
|
||||||
<DTColumnFilter
|
<DTColumnFilter
|
||||||
v-if="col.children && col.children.filter"
|
v-if="col.children && col.children.filter"
|
||||||
:field="columnProp(col, 'filterField') || columnProp(col, 'field')"
|
:field="columnProp(col, 'filterField') || columnProp(col, 'field')"
|
||||||
|
@ -54,6 +55,7 @@
|
||||||
:filterApplyTemplate="col.children && col.children.filterapply"
|
:filterApplyTemplate="col.children && col.children.filterapply"
|
||||||
:filters="filters"
|
:filters="filters"
|
||||||
:filtersStore="filtersStore"
|
:filtersStore="filtersStore"
|
||||||
|
:filterInputProps="filterInputProps"
|
||||||
@filter-change="$emit('filter-change', $event)"
|
@filter-change="$emit('filter-change', $event)"
|
||||||
@filter-apply="$emit('filter-apply')"
|
@filter-apply="$emit('filter-apply')"
|
||||||
:filterMenuStyle="columnProp(col, 'filterMenuStyle')"
|
:filterMenuStyle="columnProp(col, 'filterMenuStyle')"
|
||||||
|
@ -110,10 +112,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { ObjectUtils } from 'primevue/utils';
|
||||||
|
import ColumnFilter from './ColumnFilter.vue';
|
||||||
import HeaderCell from './HeaderCell.vue';
|
import HeaderCell from './HeaderCell.vue';
|
||||||
import HeaderCheckbox from './HeaderCheckbox.vue';
|
import HeaderCheckbox from './HeaderCheckbox.vue';
|
||||||
import ColumnFilter from './ColumnFilter.vue';
|
|
||||||
import { ObjectUtils } from 'primevue/utils';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TableHeader',
|
name: 'TableHeader',
|
||||||
|
@ -149,7 +151,7 @@ export default {
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
groupRowsBy: {
|
groupRowsBy: {
|
||||||
type: [Array, String],
|
type: [Array, String, Function],
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
resizableColumns: {
|
resizableColumns: {
|
||||||
|
@ -199,6 +201,10 @@ export default {
|
||||||
reorderableColumns: {
|
reorderableColumns: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
filterInputProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<tbody class="p-datatable-tbody">
|
<tbody class="p-datatable-tbody">
|
||||||
<tr v-for="n in rows" :key="n">
|
<tr v-for="n in rows" :key="n" role="row">
|
||||||
<td v-for="(col, i) of columns" :key="col.props.columnKey || col.props.field || i">
|
<td v-for="(col, i) of columns" :key="col.props.columnKey || col.props.field || i">
|
||||||
<component v-if="col.children && col.children.loading" :is="col.children.loading" :column="col" :index="i" />
|
<component v-if="col.children && col.children.loading" :is="col.children.loading" :column="col" :index="i" />
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
|
import PrimeVue from 'primevue/config';
|
||||||
import DataView from './DataView.vue';
|
import DataView from './DataView.vue';
|
||||||
|
|
||||||
describe('DataView.vue', () => {
|
describe('DataView.vue', () => {
|
||||||
it('should exist', () => {
|
it('should exist', () => {
|
||||||
const wrapper = mount(DataView, {
|
const wrapper = mount(DataView, {
|
||||||
|
global: {
|
||||||
|
plugins: [PrimeVue]
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,19 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { config, mount } from '@vue/test-utils';
|
||||||
import DataViewLayoutOptions from './DataViewLayoutOptions.vue';
|
import DataViewLayoutOptions from './DataViewLayoutOptions.vue';
|
||||||
|
|
||||||
|
config.global.mocks = {
|
||||||
|
$primevue: {
|
||||||
|
config: {
|
||||||
|
locale: {
|
||||||
|
aria: {
|
||||||
|
listView: 'listView',
|
||||||
|
gridView: 'gridView'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
describe('DataViewLayoutOptions.vue', () => {
|
describe('DataViewLayoutOptions.vue', () => {
|
||||||
it('should exist', async () => {
|
it('should exist', async () => {
|
||||||
const wrapper = mount(DataViewLayoutOptions, {
|
const wrapper = mount(DataViewLayoutOptions, {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="p-dataview-layout-options p-selectbutton p-buttonset">
|
<div class="p-dataview-layout-options p-selectbutton p-buttonset" role="group">
|
||||||
<button :class="buttonListClass" @click="changeLayout('list')" type="button">
|
<button :aria-label="listViewAriaLabel" :class="buttonListClass" @click="changeLayout('list')" type="button" :aria-pressed="isListButtonPressed">
|
||||||
<i class="pi pi-bars"></i>
|
<i class="pi pi-bars"></i>
|
||||||
</button>
|
</button>
|
||||||
<button :class="buttonGridClass" @click="changeLayout('grid')" type="button">
|
<button :aria-label="gridViewAriaLabel" :class="buttonGridClass" @click="changeLayout('grid')" type="button" :aria-pressed="isGridButtonPressed">
|
||||||
<i class="pi pi-th-large"></i>
|
<i class="pi pi-th-large"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,9 +16,23 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
modelValue: String
|
modelValue: String
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isListButtonPressed: false,
|
||||||
|
isGridButtonPressed: false
|
||||||
|
};
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
changeLayout(layout) {
|
changeLayout(layout) {
|
||||||
this.$emit('update:modelValue', layout);
|
this.$emit('update:modelValue', layout);
|
||||||
|
|
||||||
|
if (layout === 'list') {
|
||||||
|
this.isListButtonPressed = true;
|
||||||
|
this.isGridButtonPressed = false;
|
||||||
|
} else if (layout === 'grid') {
|
||||||
|
this.isGridButtonPressed = true;
|
||||||
|
this.isListButtonPressed = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -27,6 +41,12 @@ export default {
|
||||||
},
|
},
|
||||||
buttonGridClass() {
|
buttonGridClass() {
|
||||||
return ['p-button p-button-icon-only', { 'p-highlight': this.modelValue === 'grid' }];
|
return ['p-button p-button-icon-only', { 'p-highlight': this.modelValue === 'grid' }];
|
||||||
|
},
|
||||||
|
listViewAriaLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.listView : undefined;
|
||||||
|
},
|
||||||
|
gridViewAriaLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.gridView : undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { VNode } from 'vue';
|
import { HTMLAttributes, VNode } from 'vue';
|
||||||
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
||||||
|
|
||||||
type DialogPositionType = 'center' | 'top' | 'bottom' | 'left' | 'right' | 'topleft' | 'topright' | 'bottomleft' | 'bottomright' | undefined;
|
type DialogPositionType = 'center' | 'top' | 'bottom' | 'left' | 'right' | 'topleft' | 'topright' | 'bottomleft' | 'bottomright' | undefined;
|
||||||
|
@ -49,6 +49,10 @@ export interface DialogProps {
|
||||||
* Style class of the content section.
|
* Style class of the content section.
|
||||||
*/
|
*/
|
||||||
contentClass?: any;
|
contentClass?: any;
|
||||||
|
/**
|
||||||
|
* Uses to pass all properties of the HTMLDivElement to the overlay panel inside the component.
|
||||||
|
*/
|
||||||
|
contentProps?: HTMLAttributes | undefined;
|
||||||
/**
|
/**
|
||||||
* When enabled dialog is displayed in RTL direction.
|
* When enabled dialog is displayed in RTL direction.
|
||||||
*/
|
*/
|
||||||
|
@ -82,11 +86,6 @@ export interface DialogProps {
|
||||||
* Default value is true.
|
* Default value is true.
|
||||||
*/
|
*/
|
||||||
autoZIndex?: boolean | undefined;
|
autoZIndex?: boolean | undefined;
|
||||||
/**
|
|
||||||
* Aria label of the close icon.
|
|
||||||
* Default value is 'close'.
|
|
||||||
*/
|
|
||||||
ariaCloseLabel?: string | undefined;
|
|
||||||
/**
|
/**
|
||||||
* Position of the dialog, options are 'center', 'top', 'bottom', 'left', 'right', 'topleft', 'topright', 'bottomleft' or 'bottomright'.
|
* Position of the dialog, options are 'center', 'top', 'bottom', 'left', 'right', 'topleft', 'topright', 'bottomleft' or 'bottomright'.
|
||||||
* @see DialogPositionType
|
* @see DialogPositionType
|
||||||
|
@ -132,6 +131,21 @@ export interface DialogProps {
|
||||||
* Style of the dynamic dialog.
|
* Style of the dynamic dialog.
|
||||||
*/
|
*/
|
||||||
style?: any;
|
style?: any;
|
||||||
|
/**
|
||||||
|
* Icon to display in the dialog close button.
|
||||||
|
* Default value is 'pi pi-times'.
|
||||||
|
*/
|
||||||
|
closeIcon?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to display in the dialog maximize button when dialog is not maximized.
|
||||||
|
* Default value is 'pi pi-window-maximize'.
|
||||||
|
*/
|
||||||
|
maximizeIcon?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to display in the dialog maximize button when dialog is maximized.
|
||||||
|
* Default value is 'pi pi-window-minimize'.
|
||||||
|
*/
|
||||||
|
minimizeIcon?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DialogSlots {
|
export interface DialogSlots {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import PrimeVue from '../config/PrimeVue';
|
import PrimeVue from 'primevue/config';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import Dialog from './Dialog.vue';
|
import Dialog from './Dialog.vue';
|
||||||
|
|
||||||
|
@ -8,11 +8,17 @@ describe('Dialog.vue', () => {
|
||||||
global: {
|
global: {
|
||||||
plugins: [PrimeVue],
|
plugins: [PrimeVue],
|
||||||
stubs: {
|
stubs: {
|
||||||
teleport: true
|
teleport: true,
|
||||||
|
transition: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
visible: false
|
visible: false
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
containerVisible: true
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -31,16 +37,86 @@ describe('Dialog.vue', () => {
|
||||||
teleport: true
|
teleport: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
|
||||||
visible: true
|
|
||||||
},
|
|
||||||
slots: {
|
slots: {
|
||||||
default: '<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit</p>',
|
default: '<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit</p>',
|
||||||
footer: '<p>Dialog Footer</p>'
|
footer: '<p>Dialog Footer</p>'
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
containerVisible: true
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(wrapper.find('.p-dialog-content').exists()).toBe(false);
|
await wrapper.setProps({ visible: true });
|
||||||
expect(wrapper.find('.p-dialog-footer').exists()).toBe(false);
|
|
||||||
|
expect(wrapper.find('.p-dialog-content').exists()).toBe(true);
|
||||||
|
expect(wrapper.find('.p-dialog-footer').exists()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('closable', () => {
|
||||||
|
it('should have custom close icon when provided', async () => {
|
||||||
|
const wrapper = mount(Dialog, {
|
||||||
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
|
stubs: {
|
||||||
|
teleport: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
closable: true,
|
||||||
|
closeIcon: 'pi pi-discord',
|
||||||
|
showHeader: true,
|
||||||
|
visible: false
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
containerVisible: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await wrapper.setProps({ visible: true });
|
||||||
|
|
||||||
|
const icon = wrapper.find('.p-dialog-header-close-icon');
|
||||||
|
|
||||||
|
expect(icon.classes()).toContain('pi-discord');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('maximizable', () => {
|
||||||
|
it('should have custom maximize and minimize icons when provided', async () => {
|
||||||
|
const wrapper = mount(Dialog, {
|
||||||
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
|
stubs: {
|
||||||
|
teleport: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
maximizable: true,
|
||||||
|
maximizeIcon: 'pi pi-discord',
|
||||||
|
minimizeIcon: 'pi pi-facebook',
|
||||||
|
showHeader: true,
|
||||||
|
visible: false
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
containerVisible: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await wrapper.setProps({ visible: true });
|
||||||
|
await wrapper.setData({ maximized: false });
|
||||||
|
|
||||||
|
const icon = wrapper.find('.p-dialog-header-maximize-icon');
|
||||||
|
|
||||||
|
expect(icon.classes()).toContain('pi-discord');
|
||||||
|
|
||||||
|
await wrapper.setData({ maximized: true });
|
||||||
|
|
||||||
|
expect(icon.classes()).toContain('pi-facebook');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,24 +2,24 @@
|
||||||
<Portal :appendTo="appendTo">
|
<Portal :appendTo="appendTo">
|
||||||
<div v-if="containerVisible" :ref="maskRef" :class="maskClass" @click="onMaskClick">
|
<div v-if="containerVisible" :ref="maskRef" :class="maskClass" @click="onMaskClick">
|
||||||
<transition name="p-dialog" @before-enter="onBeforeEnter" @enter="onEnter" @before-leave="onBeforeLeave" @leave="onLeave" @after-leave="onAfterLeave" appear>
|
<transition name="p-dialog" @before-enter="onBeforeEnter" @enter="onEnter" @before-leave="onBeforeLeave" @leave="onLeave" @after-leave="onAfterLeave" appear>
|
||||||
<div v-if="visible" :ref="containerRef" :class="dialogClass" v-bind="$attrs" role="dialog" :aria-labelledby="ariaLabelledById" :aria-modal="modal">
|
<div v-if="visible" :ref="containerRef" v-focustrap="{ disabled: !modal }" :class="dialogClass" role="dialog" :aria-labelledby="ariaLabelledById" :aria-modal="modal" v-bind="$attrs">
|
||||||
<div v-if="showHeader" class="p-dialog-header" @mousedown="initDrag">
|
<div v-if="showHeader" :ref="headerContainerRef" class="p-dialog-header" @mousedown="initDrag">
|
||||||
<slot name="header">
|
<slot name="header">
|
||||||
<span v-if="header" :id="ariaLabelledById" class="p-dialog-title">{{ header }}</span>
|
<span v-if="header" :id="ariaLabelledById" class="p-dialog-title">{{ header }}</span>
|
||||||
</slot>
|
</slot>
|
||||||
<div class="p-dialog-header-icons">
|
<div class="p-dialog-header-icons">
|
||||||
<button v-if="maximizable" v-ripple class="p-dialog-header-icon p-dialog-header-maximize p-link" @click="maximize" type="button" tabindex="-1">
|
<button v-if="maximizable" :ref="maximizableRef" v-ripple :autofocus="focusable" class="p-dialog-header-icon p-dialog-header-maximize p-link" @click="maximize" type="button" :tabindex="maximizable ? '0' : '-1'">
|
||||||
<span :class="maximizeIconClass"></span>
|
<span :class="maximizeIconClass"></span>
|
||||||
</button>
|
</button>
|
||||||
<button v-if="closable" v-ripple class="p-dialog-header-icon p-dialog-header-close p-link" @click="close" :aria-label="ariaCloseLabel" type="button">
|
<button v-if="closable" :ref="closeButtonRef" v-ripple :autofocus="focusable" class="p-dialog-header-icon p-dialog-header-close p-link" @click="close" :aria-label="closeAriaLabel" type="button" v-bind="closeButtonProps">
|
||||||
<span class="p-dialog-header-close-icon pi pi-times"></span>
|
<span :class="['p-dialog-header-close-icon', closeIcon]"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="contentStyleClass" :style="contentStyle">
|
<div :ref="contentRef" :class="contentStyleClass" :style="contentStyle" v-bind="contentProps">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="footer || $slots.footer" class="p-dialog-footer">
|
<div v-if="footer || $slots.footer" :ref="footerContainerRef" class="p-dialog-footer">
|
||||||
<slot name="footer">{{ footer }}</slot>
|
<slot name="footer">{{ footer }}</slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,25 +29,57 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { computed } from 'vue';
|
import FocusTrap from 'primevue/focustrap';
|
||||||
import { UniqueComponentId, DomHandler, ZIndexUtils } from 'primevue/utils';
|
|
||||||
import Ripple from 'primevue/ripple';
|
|
||||||
import Portal from 'primevue/portal';
|
import Portal from 'primevue/portal';
|
||||||
|
import Ripple from 'primevue/ripple';
|
||||||
|
import { DomHandler, UniqueComponentId, ZIndexUtils } from 'primevue/utils';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Dialog',
|
name: 'Dialog',
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
emits: ['update:visible', 'show', 'hide', 'after-hide', 'maximize', 'unmaximize', 'dragend'],
|
emits: ['update:visible', 'show', 'hide', 'after-hide', 'maximize', 'unmaximize', 'dragend'],
|
||||||
props: {
|
props: {
|
||||||
header: null,
|
header: {
|
||||||
footer: null,
|
type: null,
|
||||||
visible: Boolean,
|
default: null
|
||||||
modal: Boolean,
|
},
|
||||||
contentStyle: null,
|
footer: {
|
||||||
contentClass: String,
|
type: null,
|
||||||
rtl: Boolean,
|
default: null
|
||||||
maximizable: Boolean,
|
},
|
||||||
dismissableMask: Boolean,
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
modal: {
|
||||||
|
type: Boolean,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
contentStyle: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
contentClass: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
contentProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
rtl: {
|
||||||
|
type: Boolean,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
maximizable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
dismissableMask: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
closable: {
|
closable: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
|
@ -68,10 +100,6 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
ariaCloseLabel: {
|
|
||||||
type: String,
|
|
||||||
default: 'close'
|
|
||||||
},
|
|
||||||
position: {
|
position: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'center'
|
default: 'center'
|
||||||
|
@ -100,6 +128,22 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'body'
|
default: 'body'
|
||||||
},
|
},
|
||||||
|
closeIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'pi pi-times'
|
||||||
|
},
|
||||||
|
maximizeIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'pi pi-window-maximize'
|
||||||
|
},
|
||||||
|
minimizeIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'pi pi-window-minimize'
|
||||||
|
},
|
||||||
|
closeButtonProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
_instance: null
|
_instance: null
|
||||||
},
|
},
|
||||||
provide() {
|
provide() {
|
||||||
|
@ -110,12 +154,18 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
containerVisible: this.visible,
|
containerVisible: this.visible,
|
||||||
maximized: false
|
maximized: false,
|
||||||
|
focusable: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
documentKeydownListener: null,
|
documentKeydownListener: null,
|
||||||
container: null,
|
container: null,
|
||||||
mask: null,
|
mask: null,
|
||||||
|
content: null,
|
||||||
|
headerContainer: null,
|
||||||
|
footerContainer: null,
|
||||||
|
maximizableButton: null,
|
||||||
|
closeButton: null,
|
||||||
styleElement: null,
|
styleElement: null,
|
||||||
dragging: null,
|
dragging: null,
|
||||||
documentDragListener: null,
|
documentDragListener: null,
|
||||||
|
@ -168,6 +218,7 @@ export default {
|
||||||
},
|
},
|
||||||
onLeave() {
|
onLeave() {
|
||||||
this.$emit('hide');
|
this.$emit('hide');
|
||||||
|
this.focusable = false;
|
||||||
},
|
},
|
||||||
onAfterLeave() {
|
onAfterLeave() {
|
||||||
if (this.autoZIndex) {
|
if (this.autoZIndex) {
|
||||||
|
@ -185,9 +236,26 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
focus() {
|
focus() {
|
||||||
let focusTarget = this.container.querySelector('[autofocus]');
|
const findFocusableElement = (container) => {
|
||||||
|
return container.querySelector('[autofocus]');
|
||||||
|
};
|
||||||
|
|
||||||
|
let focusTarget = this.$slots.footer && findFocusableElement(this.footerContainer);
|
||||||
|
|
||||||
|
if (!focusTarget) {
|
||||||
|
focusTarget = this.$slots.header && findFocusableElement(this.headerContainer);
|
||||||
|
|
||||||
|
if (!focusTarget) {
|
||||||
|
focusTarget = this.$slots.default && findFocusableElement(this.content);
|
||||||
|
|
||||||
|
if (!focusTarget) {
|
||||||
|
focusTarget = findFocusableElement(this.container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (focusTarget) {
|
if (focusTarget) {
|
||||||
|
this.focusable = true;
|
||||||
focusTarget.focus();
|
focusTarget.focus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -216,26 +284,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onKeyDown(event) {
|
onKeyDown(event) {
|
||||||
if (event.which === 9) {
|
if (event.code === 'Escape' && this.closeOnEscape) {
|
||||||
event.preventDefault();
|
|
||||||
let focusableElements = DomHandler.getFocusableElements(this.container);
|
|
||||||
|
|
||||||
if (focusableElements && focusableElements.length > 0) {
|
|
||||||
if (!document.activeElement) {
|
|
||||||
focusableElements[0].focus();
|
|
||||||
} else {
|
|
||||||
let focusedIndex = focusableElements.indexOf(document.activeElement);
|
|
||||||
|
|
||||||
if (event.shiftKey) {
|
|
||||||
if (focusedIndex == -1 || focusedIndex === 0) focusableElements[focusableElements.length - 1].focus();
|
|
||||||
else focusableElements[focusedIndex - 1].focus();
|
|
||||||
} else {
|
|
||||||
if (focusedIndex == -1 || focusedIndex === focusableElements.length - 1) focusableElements[0].focus();
|
|
||||||
else focusableElements[focusedIndex + 1].focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (event.which === 27 && this.closeOnEscape) {
|
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -263,6 +312,21 @@ export default {
|
||||||
maskRef(el) {
|
maskRef(el) {
|
||||||
this.mask = el;
|
this.mask = el;
|
||||||
},
|
},
|
||||||
|
contentRef(el) {
|
||||||
|
this.content = el;
|
||||||
|
},
|
||||||
|
headerContainerRef(el) {
|
||||||
|
this.headerContainer = el;
|
||||||
|
},
|
||||||
|
footerContainerRef(el) {
|
||||||
|
this.footerContainer = el;
|
||||||
|
},
|
||||||
|
maximizableRef(el) {
|
||||||
|
this.maximizableButton = el;
|
||||||
|
},
|
||||||
|
closeButtonRef(el) {
|
||||||
|
this.closeButton = el;
|
||||||
|
},
|
||||||
createStyle() {
|
createStyle() {
|
||||||
if (!this.styleElement) {
|
if (!this.styleElement) {
|
||||||
this.styleElement = document.createElement('style');
|
this.styleElement = document.createElement('style');
|
||||||
|
@ -396,10 +460,10 @@ export default {
|
||||||
},
|
},
|
||||||
maximizeIconClass() {
|
maximizeIconClass() {
|
||||||
return [
|
return [
|
||||||
'p-dialog-header-maximize-icon pi',
|
'p-dialog-header-maximize-icon',
|
||||||
{
|
{
|
||||||
'pi-window-maximize': !this.maximized,
|
[this.maximizeIcon]: !this.maximized,
|
||||||
'pi-window-minimize': this.maximized
|
[this.minimizeIcon]: this.maximized
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
@ -407,7 +471,10 @@ export default {
|
||||||
return UniqueComponentId();
|
return UniqueComponentId();
|
||||||
},
|
},
|
||||||
ariaLabelledById() {
|
ariaLabelledById() {
|
||||||
return this.header != null ? this.ariaId + '_header' : null;
|
return this.header != null || this.$attrs['aria-labelledby'] !== null ? this.ariaId + '_header' : null;
|
||||||
|
},
|
||||||
|
closeAriaLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.close : undefined;
|
||||||
},
|
},
|
||||||
attributeSelector() {
|
attributeSelector() {
|
||||||
return UniqueComponentId();
|
return UniqueComponentId();
|
||||||
|
@ -417,7 +484,8 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
ripple: Ripple
|
ripple: Ripple,
|
||||||
|
focustrap: FocusTrap
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Portal: Portal
|
Portal: Portal
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { VNode } from 'vue';
|
import { VNode } from 'vue';
|
||||||
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
|
||||||
import { MenuItem } from '../menuitem';
|
import { MenuItem } from '../menuitem';
|
||||||
|
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
||||||
|
|
||||||
type DockPositionType = 'bottom' | 'top' | 'left' | 'right' | undefined;
|
type DockPositionType = 'bottom' | 'top' | 'left' | 'right' | undefined;
|
||||||
|
|
||||||
|
@ -53,6 +53,22 @@ export interface DockProps {
|
||||||
* @see DockTooltipOptions
|
* @see DockTooltipOptions
|
||||||
*/
|
*/
|
||||||
tooltipOptions?: DockTooltipOptions;
|
tooltipOptions?: DockTooltipOptions;
|
||||||
|
/**
|
||||||
|
* Unique identifier of the menu.
|
||||||
|
*/
|
||||||
|
menuId?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Index of the element in tabbing order.
|
||||||
|
*/
|
||||||
|
tabindex?: number | string | undefined;
|
||||||
|
/**
|
||||||
|
* Establishes relationships between the component and label(s) where its value should be one or more element IDs.
|
||||||
|
*/
|
||||||
|
'aria-labelledby'?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Establishes a string value that labels the component.
|
||||||
|
*/
|
||||||
|
'aria-label'?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DockSlots {
|
export interface DockSlots {
|
||||||
|
@ -65,6 +81,10 @@ export interface DockSlots {
|
||||||
* Custom content for item.
|
* Custom content for item.
|
||||||
*/
|
*/
|
||||||
item: MenuItem;
|
item: MenuItem;
|
||||||
|
/**
|
||||||
|
* Index of the menuitem
|
||||||
|
*/
|
||||||
|
index: number;
|
||||||
}) => VNode[];
|
}) => VNode[];
|
||||||
/**
|
/**
|
||||||
* Custom icon content.
|
* Custom icon content.
|
||||||
|
@ -78,7 +98,18 @@ export interface DockSlots {
|
||||||
}) => VNode[];
|
}) => VNode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare type DockEmits = {};
|
export declare type DockEmits = {
|
||||||
|
/**
|
||||||
|
* Callback to invoke when the component receives focus.
|
||||||
|
* @param {Event} event - Browser event.
|
||||||
|
*/
|
||||||
|
focus: (event: Event) => void;
|
||||||
|
/**
|
||||||
|
* Callback to invoke when the component loses focus.
|
||||||
|
* @param {Event} event - Browser event.
|
||||||
|
*/
|
||||||
|
blur: (event: Event) => void;
|
||||||
|
};
|
||||||
|
|
||||||
declare class Dock extends ClassComponent<DockProps, DockSlots, DockEmits> {}
|
declare class Dock extends ClassComponent<DockProps, DockSlots, DockEmits> {}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="containerClass" :style="style">
|
<div :class="containerClass" :style="style">
|
||||||
<DockSub :model="model" :templates="$slots" :exact="exact" :tooltipOptions="tooltipOptions"></DockSub>
|
<DockSub :model="model" :templates="$slots" :exact="exact" :tooltipOptions="tooltipOptions" :position="position" :menuId="menuId" :aria-label="ariaLabel" :aria-labelledby="ariaLabelledby" :tabindex="tabindex"></DockSub>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -21,6 +21,22 @@ export default {
|
||||||
exact: {
|
exact: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
|
},
|
||||||
|
menuId: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
tabindex: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
'aria-label': {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
'aria-labelledby': {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -63,7 +79,7 @@ export default {
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-dock-action {
|
.p-dock-link {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -1,41 +1,59 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="p-dock-list-container">
|
<div class="p-dock-list-container">
|
||||||
<ul ref="list" class="p-dock-list" role="menu" @mouseleave="onListMouseLeave">
|
<ul
|
||||||
<li v-for="(item, index) of model" :key="index" :class="itemClass(index)" role="none" @mouseenter="onItemMouseEnter(index)">
|
ref="list"
|
||||||
<template v-if="!templates['item']">
|
:id="id"
|
||||||
<router-link v-if="item.to && !disabled(item)" v-slot="{ navigate, href, isActive, isExactActive }" :to="item.to" custom>
|
class="p-dock-list"
|
||||||
<a
|
role="menu"
|
||||||
v-tooltip:[tooltipOptions]="{ value: item.label, disabled: !tooltipOptions }"
|
:aria-orientation="position === 'bottom' || position === 'top' ? 'horizontal' : 'vertical'"
|
||||||
:href="href"
|
:aria-activedescendant="focused ? focusedOptionId : undefined"
|
||||||
role="menuitem"
|
:tabindex="tabindex"
|
||||||
:class="linkClass(item, { isActive, isExactActive })"
|
:aria-label="ariaLabel"
|
||||||
:target="item.target"
|
:aria-labelledby="ariaLabelledby"
|
||||||
@click="onItemClick($event, item, navigate)"
|
@focus="onListFocus"
|
||||||
>
|
@blur="onListBlur"
|
||||||
<template v-if="!templates['icon']">
|
@keydown="onListKeyDown"
|
||||||
<span v-ripple :class="['p-dock-action-icon', item.icon]"></span>
|
@mouseleave="onListMouseLeave"
|
||||||
</template>
|
>
|
||||||
<component v-else :is="templates['icon']" :item="item"></component>
|
<template v-for="(processedItem, index) of model" :key="index">
|
||||||
</a>
|
<li
|
||||||
</router-link>
|
:id="getItemId(index)"
|
||||||
<a
|
:class="itemClass(processedItem, index, getItemId(index))"
|
||||||
v-else
|
role="menuitem"
|
||||||
v-tooltip:[tooltipOptions]="{ value: item.label, disabled: !tooltipOptions }"
|
:aria-label="processedItem.label"
|
||||||
:href="item.url"
|
:aria-disabled="disabled(processedItem)"
|
||||||
role="menuitem"
|
@click="onItemClick($event, processedItem)"
|
||||||
:class="linkClass(item)"
|
@mouseenter="onItemMouseEnter(index)"
|
||||||
:target="item.target"
|
>
|
||||||
@click="onItemClick($event, item)"
|
<div class="p-menuitem-content">
|
||||||
:tabindex="disabled(item) ? null : '0'"
|
<template v-if="!templates['item']">
|
||||||
>
|
<router-link v-if="processedItem.to && !disabled(processedItem)" v-slot="{ navigate, href, isActive, isExactActive }" :to="processedItem.to" custom>
|
||||||
<template v-if="!templates['icon']">
|
<a
|
||||||
<span v-ripple :class="['p-dock-action-icon', item.icon]"></span>
|
v-tooltip:[tooltipOptions]="{ value: processedItem.label, disabled: !tooltipOptions }"
|
||||||
|
:href="href"
|
||||||
|
:class="linkClass({ isActive, isExactActive })"
|
||||||
|
:target="processedItem.target"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-hidden="true"
|
||||||
|
@click="onItemActionClick($event, processedItem, navigate)"
|
||||||
|
>
|
||||||
|
<template v-if="!templates['icon']">
|
||||||
|
<span v-ripple :class="['p-dock-icon', processedItem.icon]"></span>
|
||||||
|
</template>
|
||||||
|
<component v-else :is="templates['icon']" :item="processedItem"></component>
|
||||||
|
</a>
|
||||||
|
</router-link>
|
||||||
|
<a v-else v-tooltip:[tooltipOptions]="{ value: processedItem.label, disabled: !tooltipOptions }" :href="processedItem.url" :class="linkClass()" :target="processedItem.target" tabindex="-1" aria-hidden="true">
|
||||||
|
<template v-if="!templates['icon']">
|
||||||
|
<span v-ripple :class="['p-dock-icon', processedItem.icon]"></span>
|
||||||
|
</template>
|
||||||
|
<component v-else :is="templates['icon']" :item="processedItem"></component>
|
||||||
|
</a>
|
||||||
</template>
|
</template>
|
||||||
<component v-else :is="templates['icon']" :item="item"></component>
|
<component v-else :is="templates['item']" :item="processedItem" :index="index"></component>
|
||||||
</a>
|
</div>
|
||||||
</template>
|
</li>
|
||||||
<component v-else :is="templates['item']" :item="item"></component>
|
</template>
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -43,10 +61,16 @@
|
||||||
<script>
|
<script>
|
||||||
import Ripple from 'primevue/ripple';
|
import Ripple from 'primevue/ripple';
|
||||||
import Tooltip from 'primevue/tooltip';
|
import Tooltip from 'primevue/tooltip';
|
||||||
|
import { DomHandler, ObjectUtils, UniqueComponentId } from 'primevue/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DockSub',
|
name: 'DockSub',
|
||||||
|
emits: ['focus', 'blur'],
|
||||||
props: {
|
props: {
|
||||||
|
position: {
|
||||||
|
type: String,
|
||||||
|
default: 'bottom'
|
||||||
|
},
|
||||||
model: {
|
model: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: null
|
default: null
|
||||||
|
@ -59,42 +83,164 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
tooltipOptions: null
|
tooltipOptions: null,
|
||||||
|
menuId: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
tabindex: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
'aria-label': {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
'aria-labelledby': {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
currentIndex: -3
|
currentIndex: -3,
|
||||||
|
focused: false,
|
||||||
|
focusedOptionIndex: -1
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getItemId(index) {
|
||||||
|
return `${this.id}_${index}`;
|
||||||
|
},
|
||||||
|
getItemProp(processedItem, name) {
|
||||||
|
return processedItem && processedItem.item ? ObjectUtils.getItemValue(processedItem.item[name]) : undefined;
|
||||||
|
},
|
||||||
|
isSameMenuItem(event) {
|
||||||
|
return event.currentTarget && (event.currentTarget.isSameNode(event.target) || event.currentTarget.isSameNode(event.target.closest('.p-menuitem')));
|
||||||
|
},
|
||||||
onListMouseLeave() {
|
onListMouseLeave() {
|
||||||
this.currentIndex = -3;
|
this.currentIndex = -3;
|
||||||
},
|
},
|
||||||
onItemMouseEnter(index) {
|
onItemMouseEnter(index) {
|
||||||
this.currentIndex = index;
|
this.currentIndex = index;
|
||||||
},
|
},
|
||||||
onItemClick(event, item, navigate) {
|
onItemActionClick(event, navigate) {
|
||||||
if (this.disabled(item)) {
|
navigate && navigate(event);
|
||||||
event.preventDefault();
|
},
|
||||||
|
onItemClick(event, processedItem) {
|
||||||
|
if (this.isSameMenuItem(event)) {
|
||||||
|
const command = this.getItemProp(processedItem, 'command');
|
||||||
|
|
||||||
return;
|
command && command({ originalEvent: event, item: processedItem.item });
|
||||||
}
|
|
||||||
|
|
||||||
if (item.command) {
|
|
||||||
item.command({
|
|
||||||
originalEvent: event,
|
|
||||||
item: item
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.to && navigate) {
|
|
||||||
navigate(event);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
itemClass(index) {
|
onListFocus(event) {
|
||||||
|
this.focused = true;
|
||||||
|
this.changeFocusedOptionIndex(0);
|
||||||
|
this.$emit('focus', event);
|
||||||
|
},
|
||||||
|
onListBlur(event) {
|
||||||
|
this.focused = false;
|
||||||
|
this.focusedOptionIndex = -1;
|
||||||
|
this.$emit('blur', event);
|
||||||
|
},
|
||||||
|
onListKeyDown(event) {
|
||||||
|
switch (event.code) {
|
||||||
|
case 'ArrowDown': {
|
||||||
|
if (this.position === 'left' || this.position === 'right') this.onArrowDownKey();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'ArrowUp': {
|
||||||
|
if (this.position === 'left' || this.position === 'right') this.onArrowUpKey();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'ArrowRight': {
|
||||||
|
if (this.position === 'top' || this.position === 'bottom') this.onArrowDownKey();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'ArrowLeft': {
|
||||||
|
if (this.position === 'top' || this.position === 'bottom') this.onArrowUpKey();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'Home': {
|
||||||
|
this.onHomeKey();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'End': {
|
||||||
|
this.onEndKey();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'Enter':
|
||||||
|
|
||||||
|
case 'Space': {
|
||||||
|
this.onSpaceKey(event);
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onArrowDownKey() {
|
||||||
|
const optionIndex = this.findNextOptionIndex(this.focusedOptionIndex);
|
||||||
|
|
||||||
|
this.changeFocusedOptionIndex(optionIndex);
|
||||||
|
},
|
||||||
|
onArrowUpKey() {
|
||||||
|
const optionIndex = this.findPrevOptionIndex(this.focusedOptionIndex);
|
||||||
|
|
||||||
|
this.changeFocusedOptionIndex(optionIndex);
|
||||||
|
},
|
||||||
|
onHomeKey() {
|
||||||
|
this.changeFocusedOptionIndex(0);
|
||||||
|
},
|
||||||
|
onEndKey() {
|
||||||
|
this.changeFocusedOptionIndex(DomHandler.find(this.$refs.list, 'li.p-dock-item:not(.p-disabled)').length - 1);
|
||||||
|
},
|
||||||
|
onSpaceKey() {
|
||||||
|
const element = DomHandler.findSingle(this.$refs.list, `li[id="${`${this.focusedOptionIndex}`}"]`);
|
||||||
|
const anchorElement = element && DomHandler.findSingle(element, '.p-dock-link');
|
||||||
|
|
||||||
|
anchorElement ? anchorElement.click() : element && element.click();
|
||||||
|
},
|
||||||
|
findNextOptionIndex(index) {
|
||||||
|
const menuitems = DomHandler.find(this.$refs.list, 'li.p-dock-item:not(.p-disabled)');
|
||||||
|
const matchedOptionIndex = [...menuitems].findIndex((link) => link.id === index);
|
||||||
|
|
||||||
|
return matchedOptionIndex > -1 ? matchedOptionIndex + 1 : 0;
|
||||||
|
},
|
||||||
|
findPrevOptionIndex(index) {
|
||||||
|
const menuitems = DomHandler.find(this.$refs.list, 'li.p-dock-item:not(.p-disabled)');
|
||||||
|
const matchedOptionIndex = [...menuitems].findIndex((link) => link.id === index);
|
||||||
|
|
||||||
|
return matchedOptionIndex > -1 ? matchedOptionIndex - 1 : 0;
|
||||||
|
},
|
||||||
|
changeFocusedOptionIndex(index) {
|
||||||
|
const menuitems = DomHandler.find(this.$refs.list, 'li.p-dock-item:not(.p-disabled)');
|
||||||
|
|
||||||
|
let order = index >= menuitems.length ? menuitems.length - 1 : index < 0 ? 0 : index;
|
||||||
|
|
||||||
|
this.focusedOptionIndex = menuitems[order].getAttribute('id');
|
||||||
|
},
|
||||||
|
itemClass(item, index, id) {
|
||||||
return [
|
return [
|
||||||
'p-dock-item',
|
'p-dock-item',
|
||||||
{
|
{
|
||||||
|
'p-focus': id === this.focusedOptionIndex,
|
||||||
|
'p-disabled': this.disabled(item),
|
||||||
'p-dock-item-second-prev': this.currentIndex - 2 === index,
|
'p-dock-item-second-prev': this.currentIndex - 2 === index,
|
||||||
'p-dock-item-prev': this.currentIndex - 1 === index,
|
'p-dock-item-prev': this.currentIndex - 1 === index,
|
||||||
'p-dock-item-current': this.currentIndex === index,
|
'p-dock-item-current': this.currentIndex === index,
|
||||||
|
@ -103,11 +249,10 @@ export default {
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
linkClass(item, routerProps) {
|
linkClass(routerProps) {
|
||||||
return [
|
return [
|
||||||
'p-dock-action',
|
'p-dock-link',
|
||||||
{
|
{
|
||||||
'p-disabled': this.disabled(item),
|
|
||||||
'router-link-active': routerProps && routerProps.isActive,
|
'router-link-active': routerProps && routerProps.isActive,
|
||||||
'router-link-active-exact': this.exact && routerProps && routerProps.isExactActive
|
'router-link-active-exact': this.exact && routerProps && routerProps.isExactActive
|
||||||
}
|
}
|
||||||
|
@ -117,6 +262,14 @@ export default {
|
||||||
return typeof item.disabled === 'function' ? item.disabled() : item.disabled;
|
return typeof item.disabled === 'function' ? item.disabled() : item.disabled;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
id() {
|
||||||
|
return this.menuId || UniqueComponentId();
|
||||||
|
},
|
||||||
|
focusedOptionId() {
|
||||||
|
return this.focusedOptionIndex !== -1 ? this.focusedOptionIndex : null;
|
||||||
|
}
|
||||||
|
},
|
||||||
directives: {
|
directives: {
|
||||||
ripple: Ripple,
|
ripple: Ripple,
|
||||||
tooltip: Tooltip
|
tooltip: Tooltip
|
||||||
|
|
|
@ -163,6 +163,21 @@ export interface DropdownProps {
|
||||||
* Whether the dropdown is in loading state.
|
* Whether the dropdown is in loading state.
|
||||||
*/
|
*/
|
||||||
loading?: boolean | undefined;
|
loading?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to display in clear button.
|
||||||
|
* Default value is 'pi pi-times'.
|
||||||
|
*/
|
||||||
|
clearIcon?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to display in the dropdown.
|
||||||
|
* Default value is 'pi pi-chevron-down'.
|
||||||
|
*/
|
||||||
|
dropdownIcon?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to display in filter input.
|
||||||
|
* Default value is 'pi pi-search'.
|
||||||
|
*/
|
||||||
|
filterIcon?: string | undefined;
|
||||||
/**
|
/**
|
||||||
* Icon to display in loading state.
|
* Icon to display in loading state.
|
||||||
* Default value is 'pi pi-spinner pi-spin'.
|
* Default value is 'pi pi-spinner pi-spin'.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import PrimeVue from '../config/PrimeVue';
|
import PrimeVue from 'primevue/config';
|
||||||
import Dropdown from '../dropdown/Dropdown.vue';
|
import Dropdown from '@/components/dropdown/Dropdown.vue';
|
||||||
|
|
||||||
describe('Dropdown.vue', () => {
|
describe('Dropdown.vue', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
@ -22,10 +22,9 @@ describe('Dropdown.vue', () => {
|
||||||
it('should Dropdown exist', () => {
|
it('should Dropdown exist', () => {
|
||||||
expect(wrapper.find('.p-dropdown.p-component').exists()).toBe(true);
|
expect(wrapper.find('.p-dropdown.p-component').exists()).toBe(true);
|
||||||
expect(wrapper.find('.p-dropdown-panel').exists()).toBe(true);
|
expect(wrapper.find('.p-dropdown-panel').exists()).toBe(true);
|
||||||
|
expect(wrapper.find('.p-dropdown-empty-message').exists()).toBe(true);
|
||||||
expect(wrapper.find('.p-focus').exists()).toBe(false);
|
expect(wrapper.find('.p-focus').exists()).toBe(true);
|
||||||
expect(wrapper.find('.p-inputwrapper-filled').exists()).toBe(false);
|
expect(wrapper.find('.p-inputwrapper-filled').exists()).toBe(false);
|
||||||
|
|
||||||
expect(wrapper.find('.p-inputwrapper-focus').exists()).toBe(true);
|
expect(wrapper.find('.p-inputwrapper-focus').exists()).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -67,6 +66,32 @@ describe('option checks', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('clear checks', () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
wrapper = mount(Dropdown, {
|
||||||
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
|
stubs: {
|
||||||
|
teleport: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
clearIcon: 'pi pi-discord',
|
||||||
|
modelValue: 'value',
|
||||||
|
showClear: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await wrapper.trigger('click');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have correct icon', () => {
|
||||||
|
expect(wrapper.find('.p-dropdown-clear-icon').classes()).toContain('pi-discord');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('editable checks', () => {
|
describe('editable checks', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
|
@ -295,6 +320,7 @@ describe('filter checks', () => {
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
filter: true,
|
filter: true,
|
||||||
|
filterIcon: 'pi pi-discord',
|
||||||
options: [
|
options: [
|
||||||
{ name: 'Australia', code: 'AU' },
|
{ name: 'Australia', code: 'AU' },
|
||||||
{ name: 'Brazil', code: 'BR' },
|
{ name: 'Brazil', code: 'BR' },
|
||||||
|
@ -316,11 +342,13 @@ describe('filter checks', () => {
|
||||||
|
|
||||||
it('should make filtering', async () => {
|
it('should make filtering', async () => {
|
||||||
const filterInput = wrapper.find('.p-dropdown-filter');
|
const filterInput = wrapper.find('.p-dropdown-filter');
|
||||||
|
const filterIcon = wrapper.find('.p-dropdown-filter-icon');
|
||||||
|
|
||||||
expect(filterInput.exists()).toBe(true);
|
expect(filterInput.exists()).toBe(true);
|
||||||
|
expect(filterIcon.classes()).toContain('pi-discord');
|
||||||
|
|
||||||
const event = { target: { value: 'c' } };
|
const event = { target: { value: 'c' } };
|
||||||
const onFilterChange = vi.spyOn(wrapper.vm, 'onFilterChange');
|
const onFilterChange = jest.spyOn(wrapper.vm, 'onFilterChange');
|
||||||
|
|
||||||
wrapper.vm.onFilterChange(event);
|
wrapper.vm.onFilterChange(event);
|
||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
>
|
>
|
||||||
<slot name="value" :value="modelValue" :placeholder="placeholder">{{ label === 'p-emptylabel' ? ' ' : label || 'empty' }}</slot>
|
<slot name="value" :value="modelValue" :placeholder="placeholder">{{ label === 'p-emptylabel' ? ' ' : label || 'empty' }}</slot>
|
||||||
</span>
|
</span>
|
||||||
<i v-if="showClear && modelValue != null" class="p-dropdown-clear-icon pi pi-times" @click="onClearClick" v-bind="clearIconProps"></i>
|
<i v-if="showClear && modelValue != null" :class="['p-dropdown-clear-icon', clearIcon]" @click="onClearClick" v-bind="clearIconProps"></i>
|
||||||
<div class="p-dropdown-trigger">
|
<div class="p-dropdown-trigger">
|
||||||
<slot name="indicator">
|
<slot name="indicator">
|
||||||
<span :class="dropdownIconClass" aria-hidden="true"></span>
|
<span :class="dropdownIconClass" aria-hidden="true"></span>
|
||||||
|
@ -76,7 +76,7 @@
|
||||||
@input="onFilterChange"
|
@input="onFilterChange"
|
||||||
v-bind="filterInputProps"
|
v-bind="filterInputProps"
|
||||||
/>
|
/>
|
||||||
<span class="p-dropdown-filter-icon pi pi-search"></span>
|
<span :class="['p-dropdown-filter-icon', filterIcon]" />
|
||||||
</div>
|
</div>
|
||||||
<span role="status" aria-live="polite" class="p-hidden-accessible">
|
<span role="status" aria-live="polite" class="p-hidden-accessible">
|
||||||
{{ filterResultMessageText }}
|
{{ filterResultMessageText }}
|
||||||
|
@ -115,12 +115,6 @@
|
||||||
<slot name="empty">{{ emptyMessageText }}</slot>
|
<slot name="empty">{{ emptyMessageText }}</slot>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<span v-if="!options || (options && options.length === 0)" role="status" aria-live="polite" class="p-hidden-accessible">
|
|
||||||
{{ emptyMessageText }}
|
|
||||||
</span>
|
|
||||||
<span role="status" aria-live="polite" class="p-hidden-accessible">
|
|
||||||
{{ selectedMessageText }}
|
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-if="$slots.loader" v-slot:loader="{ options }">
|
<template v-if="$slots.loader" v-slot:loader="{ options }">
|
||||||
<slot name="loader" :options="options"></slot>
|
<slot name="loader" :options="options"></slot>
|
||||||
|
@ -128,6 +122,12 @@
|
||||||
</VirtualScroller>
|
</VirtualScroller>
|
||||||
</div>
|
</div>
|
||||||
<slot name="footer" :value="modelValue" :options="visibleOptions"></slot>
|
<slot name="footer" :value="modelValue" :options="visibleOptions"></slot>
|
||||||
|
<span v-if="!options || (options && options.length === 0)" role="status" aria-live="polite" class="p-hidden-accessible">
|
||||||
|
{{ emptyMessageText }}
|
||||||
|
</span>
|
||||||
|
<span role="status" aria-live="polite" class="p-hidden-accessible">
|
||||||
|
{{ selectedMessageText }}
|
||||||
|
</span>
|
||||||
<span ref="lastHiddenFocusableElementOnOverlay" role="presentation" aria-hidden="true" class="p-hidden-accessible p-hidden-focusable" :tabindex="0" @focus="onLastHiddenFocus"></span>
|
<span ref="lastHiddenFocusableElementOnOverlay" role="presentation" aria-hidden="true" class="p-hidden-accessible p-hidden-focusable" :tabindex="0" @focus="onLastHiddenFocus"></span>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
@ -136,12 +136,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ConnectedOverlayScrollHandler, ObjectUtils, DomHandler, ZIndexUtils, UniqueComponentId } from 'primevue/utils';
|
|
||||||
import OverlayEventBus from 'primevue/overlayeventbus';
|
|
||||||
import { FilterService } from 'primevue/api';
|
import { FilterService } from 'primevue/api';
|
||||||
import Ripple from 'primevue/ripple';
|
import OverlayEventBus from 'primevue/overlayeventbus';
|
||||||
import VirtualScroller from 'primevue/virtualscroller';
|
|
||||||
import Portal from 'primevue/portal';
|
import Portal from 'primevue/portal';
|
||||||
|
import Ripple from 'primevue/ripple';
|
||||||
|
import { ConnectedOverlayScrollHandler, DomHandler, ObjectUtils, UniqueComponentId, ZIndexUtils } from 'primevue/utils';
|
||||||
|
import VirtualScroller from 'primevue/virtualscroller';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Dropdown',
|
name: 'Dropdown',
|
||||||
|
@ -227,6 +227,18 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
clearIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'pi pi-times'
|
||||||
|
},
|
||||||
|
dropdownIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'pi pi-chevron-down'
|
||||||
|
},
|
||||||
|
filterIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'pi pi-search'
|
||||||
|
},
|
||||||
loadingIcon: {
|
loadingIcon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'pi pi-spinner pi-spin'
|
default: 'pi pi-spinner pi-spin'
|
||||||
|
@ -386,7 +398,7 @@ export default {
|
||||||
},
|
},
|
||||||
onFocus(event) {
|
onFocus(event) {
|
||||||
this.focused = true;
|
this.focused = true;
|
||||||
this.focusedOptionIndex = this.overlayVisible && this.autoOptionFocus ? this.findFirstFocusedOptionIndex() : -1;
|
this.focusedOptionIndex = this.focusedOptionIndex !== -1 ? this.focusedOptionIndex : this.overlayVisible && this.autoOptionFocus ? this.findFirstFocusedOptionIndex() : -1;
|
||||||
this.overlayVisible && this.scrollInView(this.focusedOptionIndex);
|
this.overlayVisible && this.scrollInView(this.focusedOptionIndex);
|
||||||
this.$emit('focus', event);
|
this.$emit('focus', event);
|
||||||
},
|
},
|
||||||
|
@ -434,6 +446,7 @@ export default {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
|
case 'NumpadEnter':
|
||||||
this.onEnterKey(event);
|
this.onEnterKey(event);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -488,18 +501,14 @@ export default {
|
||||||
this.updateModel(event, null);
|
this.updateModel(event, null);
|
||||||
},
|
},
|
||||||
onFirstHiddenFocus(event) {
|
onFirstHiddenFocus(event) {
|
||||||
const relatedTarget = event.relatedTarget;
|
const focusableEl = event.relatedTarget === this.$refs.focusInput ? DomHandler.getFirstFocusableElement(this.overlay, ':not(.p-hidden-focusable)') : this.$refs.focusInput;
|
||||||
|
|
||||||
if (relatedTarget === this.$refs.focusInput) {
|
DomHandler.focus(focusableEl);
|
||||||
const firstFocusableEl = DomHandler.getFirstFocusableElement(this.overlay, ':not(.p-hidden-focusable)');
|
|
||||||
|
|
||||||
DomHandler.focus(firstFocusableEl);
|
|
||||||
} else {
|
|
||||||
DomHandler.focus(this.$refs.focusInput);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onLastHiddenFocus() {
|
onLastHiddenFocus(event) {
|
||||||
DomHandler.focus(this.$refs.firstHiddenFocusableElementOnOverlay);
|
const focusableEl = event.relatedTarget === this.$refs.focusInput ? DomHandler.getLastFocusableElement(this.overlay, ':not(.p-hidden-focusable)') : this.$refs.focusInput;
|
||||||
|
|
||||||
|
DomHandler.focus(focusableEl);
|
||||||
},
|
},
|
||||||
onOptionSelect(event, option, isHide = true) {
|
onOptionSelect(event, option, isHide = true) {
|
||||||
const value = this.getOptionValue(option);
|
const value = this.getOptionValue(option);
|
||||||
|
@ -939,12 +948,31 @@ export default {
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
dropdownIconClass() {
|
dropdownIconClass() {
|
||||||
return ['p-dropdown-trigger-icon', this.loading ? this.loadingIcon : 'pi pi-chevron-down'];
|
return ['p-dropdown-trigger-icon', this.loading ? this.loadingIcon : this.dropdownIcon];
|
||||||
},
|
},
|
||||||
visibleOptions() {
|
visibleOptions() {
|
||||||
const options = this.optionGroupLabel ? this.flatOptions(this.options) : this.options || [];
|
const options = this.optionGroupLabel ? this.flatOptions(this.options) : this.options || [];
|
||||||
|
|
||||||
return this.filterValue ? FilterService.filter(options, this.searchFields, this.filterValue, this.filterMatchMode, this.filterLocale) : options;
|
if (this.filterValue) {
|
||||||
|
const filteredOptions = FilterService.filter(options, this.searchFields, this.filterValue, this.filterMatchMode, this.filterLocale);
|
||||||
|
|
||||||
|
if (this.optionGroupLabel) {
|
||||||
|
const optionGroups = this.options || [];
|
||||||
|
const filtered = [];
|
||||||
|
|
||||||
|
optionGroups.forEach((group) => {
|
||||||
|
const filteredItems = group.items.filter((item) => filteredOptions.includes(item));
|
||||||
|
|
||||||
|
if (filteredItems.length > 0) filtered.push({ ...group, items: [...filteredItems] });
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.flatOptions(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
},
|
},
|
||||||
hasSelectedOption() {
|
hasSelectedOption() {
|
||||||
return ObjectUtils.isNotEmpty(this.modelValue);
|
return ObjectUtils.isNotEmpty(this.modelValue);
|
||||||
|
|
|
@ -28,7 +28,7 @@ export interface FieldsetProps {
|
||||||
/**
|
/**
|
||||||
* Uses to pass the custom value to read for the anchor inside the component.
|
* Uses to pass the custom value to read for the anchor inside the component.
|
||||||
*/
|
*/
|
||||||
toggleButtonProps?: string | undefined;
|
toggleButtonProps?: object | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FieldsetSlots {
|
export interface FieldsetSlots {
|
||||||
|
|
|
@ -4,7 +4,19 @@
|
||||||
<slot v-if="!toggleable" name="legend">
|
<slot v-if="!toggleable" name="legend">
|
||||||
<span :id="ariaId + '_header'" class="p-fieldset-legend-text">{{ legend }}</span>
|
<span :id="ariaId + '_header'" class="p-fieldset-legend-text">{{ legend }}</span>
|
||||||
</slot>
|
</slot>
|
||||||
<a v-if="toggleable" :id="ariaId + '_header'" v-ripple tabindex="0" role="button" :aria-controls="ariaId + '_content'" :aria-expanded="!d_collapsed" :aria-label="toggleButtonProps || legend" @click="toggle" @keydown="onKeyDown">
|
<a
|
||||||
|
v-if="toggleable"
|
||||||
|
:id="ariaId + '_header'"
|
||||||
|
v-ripple
|
||||||
|
tabindex="0"
|
||||||
|
role="button"
|
||||||
|
:aria-controls="ariaId + '_content'"
|
||||||
|
:aria-expanded="!d_collapsed"
|
||||||
|
:aria-label="buttonAriaLabel"
|
||||||
|
@click="toggle"
|
||||||
|
@keydown="onKeyDown"
|
||||||
|
v-bind="toggleButtonProps"
|
||||||
|
>
|
||||||
<span :class="iconClass"></span>
|
<span :class="iconClass"></span>
|
||||||
<slot name="legend">
|
<slot name="legend">
|
||||||
<span class="p-fieldset-legend-text">{{ legend }}</span>
|
<span class="p-fieldset-legend-text">{{ legend }}</span>
|
||||||
|
@ -22,8 +34,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { UniqueComponentId } from 'primevue/utils';
|
|
||||||
import Ripple from 'primevue/ripple';
|
import Ripple from 'primevue/ripple';
|
||||||
|
import { UniqueComponentId } from 'primevue/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Fieldset',
|
name: 'Fieldset',
|
||||||
|
@ -32,7 +44,10 @@ export default {
|
||||||
legend: String,
|
legend: String,
|
||||||
toggleable: Boolean,
|
toggleable: Boolean,
|
||||||
collapsed: Boolean,
|
collapsed: Boolean,
|
||||||
toggleButtonProps: String
|
toggleButtonProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -72,6 +87,9 @@ export default {
|
||||||
},
|
},
|
||||||
ariaId() {
|
ariaId() {
|
||||||
return UniqueComponentId();
|
return UniqueComponentId();
|
||||||
|
},
|
||||||
|
buttonAriaLabel() {
|
||||||
|
return this.toggleButtonProps && this.toggleButtonProps['aria-label'] ? this.toggleButtonProps['aria-label'] : this.legend;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
<template>
|
||||||
|
<div v-for="(file, index) of files" :key="file.name + file.type + file.size" class="p-fileupload-file">
|
||||||
|
<img role="presentation" class="p-fileupload-file-thumbnail" :alt="file.name" :src="file.objectURL" :width="previewWidth" />
|
||||||
|
<div class="p-fileupload-file-details">
|
||||||
|
<div class="p-fileupload-file-name">{{ file.name }}</div>
|
||||||
|
<span class="p-fileupload-file-size">{{ formatSize(file.size) }}</span>
|
||||||
|
<FileUploadBadge :value="badgeValue" class="p-fileupload-file-badge" :severity="badgeSeverity" />
|
||||||
|
</div>
|
||||||
|
<div class="p-fileupload-file-actions">
|
||||||
|
<FileUploadButton icon="pi pi-times" @click="$emit('remove', index)" class="p-fileupload-file-remove p-button-text p-button-danger p-button-rounded"></FileUploadButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Badge from 'primevue/badge';
|
||||||
|
import Button from 'primevue/button';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
emits: ['remove'],
|
||||||
|
props: {
|
||||||
|
files: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
badgeSeverity: {
|
||||||
|
type: String,
|
||||||
|
default: 'warning'
|
||||||
|
},
|
||||||
|
badgeValue: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
previewWidth: {
|
||||||
|
type: Number,
|
||||||
|
default: 50
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formatSize(bytes) {
|
||||||
|
if (bytes === 0) {
|
||||||
|
return '0 B';
|
||||||
|
}
|
||||||
|
|
||||||
|
let k = 1000,
|
||||||
|
dm = 3,
|
||||||
|
sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
|
||||||
|
i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
FileUploadButton: Button,
|
||||||
|
FileUploadBadge: Badge
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -87,6 +87,17 @@ export interface FileUploadRemoveEvent {
|
||||||
files: File[];
|
files: File[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FileUploadRemoveUploadedFile {
|
||||||
|
/**
|
||||||
|
* Removed file.
|
||||||
|
*/
|
||||||
|
file: File;
|
||||||
|
/**
|
||||||
|
* Remaining files to be uploaded.
|
||||||
|
*/
|
||||||
|
files: File[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface FileUploadProps {
|
export interface FileUploadProps {
|
||||||
/**
|
/**
|
||||||
* Name of the request parameter to identify the files at backend.
|
* Name of the request parameter to identify the files at backend.
|
||||||
|
@ -202,7 +213,61 @@ export interface FileUploadProps {
|
||||||
|
|
||||||
export interface FileUploadSlots {
|
export interface FileUploadSlots {
|
||||||
/**
|
/**
|
||||||
* Custom empty template.
|
* Custom header content template.
|
||||||
|
*/
|
||||||
|
header: (scope: {
|
||||||
|
/**
|
||||||
|
* Files to upload.
|
||||||
|
*/
|
||||||
|
files: File[];
|
||||||
|
/**
|
||||||
|
* Uploaded files.
|
||||||
|
*/
|
||||||
|
uploadedFiles: File[];
|
||||||
|
/**
|
||||||
|
* Choose function
|
||||||
|
*/
|
||||||
|
chooseCallback: () => void;
|
||||||
|
/**
|
||||||
|
* Upload function
|
||||||
|
*/
|
||||||
|
uploadCallback: () => void;
|
||||||
|
/**
|
||||||
|
* Clear function
|
||||||
|
*/
|
||||||
|
clearCallback: () => void;
|
||||||
|
}) => VNode[];
|
||||||
|
/**
|
||||||
|
* Custom uploaded content template.
|
||||||
|
*/
|
||||||
|
content: (scope: {
|
||||||
|
/**
|
||||||
|
* Files to upload.
|
||||||
|
*/
|
||||||
|
files: File[];
|
||||||
|
/**
|
||||||
|
* Uploaded files.
|
||||||
|
*/
|
||||||
|
uploadedFiles: File[];
|
||||||
|
/**
|
||||||
|
* Function to remove an uploaded file.
|
||||||
|
*/
|
||||||
|
removeUploadedFileCallback: () => void;
|
||||||
|
/**
|
||||||
|
* Function to remove a file.
|
||||||
|
*/
|
||||||
|
removeFileCallback: () => void;
|
||||||
|
/**
|
||||||
|
* Uploaded progress as number.
|
||||||
|
*/
|
||||||
|
progress: number;
|
||||||
|
/**
|
||||||
|
* Status messages about upload process.
|
||||||
|
*/
|
||||||
|
messages: string | undefined;
|
||||||
|
}) => VNode[];
|
||||||
|
/**
|
||||||
|
* Custom content when there is no selected file.
|
||||||
*/
|
*/
|
||||||
empty: () => VNode[];
|
empty: () => VNode[];
|
||||||
}
|
}
|
||||||
|
@ -252,6 +317,11 @@ export declare type FileUploadEmits = {
|
||||||
* @param {FileUploadRemoveEvent} event - Custom remove event.
|
* @param {FileUploadRemoveEvent} event - Custom remove event.
|
||||||
*/
|
*/
|
||||||
remove: (event: FileUploadRemoveEvent) => void;
|
remove: (event: FileUploadRemoveEvent) => void;
|
||||||
|
/**
|
||||||
|
* Callback to invoke when a single uploaded file is removed from the uploaded file list.
|
||||||
|
* @param {FileUploadRemoveUploadedFile} event - Custom uploaded file remove event.
|
||||||
|
*/
|
||||||
|
removeUploadedFile: (event: FileUploadRemoveUploadedFile) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare class FileUpload extends ClassComponent<FileUploadProps, FileUploadSlots, FileUploadEmits> {}
|
declare class FileUpload extends ClassComponent<FileUploadProps, FileUploadSlots, FileUploadEmits> {}
|
||||||
|
|
|
@ -1,30 +1,24 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="isAdvanced" class="p-fileupload p-fileupload-advanced p-component">
|
<div v-if="isAdvanced" class="p-fileupload p-fileupload-advanced p-component">
|
||||||
|
<input ref="fileInput" type="file" @change="onFileSelect" :multiple="multiple" :accept="accept" :disabled="chooseDisabled" />
|
||||||
<div class="p-fileupload-buttonbar">
|
<div class="p-fileupload-buttonbar">
|
||||||
<span v-ripple :class="advancedChooseButtonClass" :style="style" @click="choose" @keydown.enter="choose" @focus="onFocus" @blur="onBlur" tabindex="0">
|
<slot name="header" :files="files" :uploadedFiles="uploadedFiles" :chooseCallback="choose" :uploadCallback="upload" :clearCallback="clear">
|
||||||
<input ref="fileInput" type="file" @change="onFileSelect" :multiple="multiple" :accept="accept" :disabled="chooseDisabled" />
|
<span v-ripple :class="advancedChooseButtonClass" :style="style" @click="choose" @keydown.enter="choose" @focus="onFocus" @blur="onBlur" tabindex="0">
|
||||||
<span :class="advancedChooseIconClass"></span>
|
<span :class="advancedChooseIconClass"></span>
|
||||||
<span class="p-button-label">{{ chooseButtonLabel }}</span>
|
<span class="p-button-label">{{ chooseButtonLabel }}</span>
|
||||||
</span>
|
</span>
|
||||||
<FileUploadButton v-if="showUploadButton" :label="uploadButtonLabel" :icon="uploadIcon" @click="upload" :disabled="uploadDisabled" />
|
<FileUploadButton v-if="showUploadButton" :label="uploadButtonLabel" :icon="uploadIcon" @click="upload" :disabled="uploadDisabled" />
|
||||||
<FileUploadButton v-if="showCancelButton" :label="cancelButtonLabel" :icon="cancelIcon" @click="clear" :disabled="cancelDisabled" />
|
<FileUploadButton v-if="showCancelButton" :label="cancelButtonLabel" :icon="cancelIcon" @click="clear" :disabled="cancelDisabled" />
|
||||||
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
<div ref="content" class="p-fileupload-content" @dragenter="onDragEnter" @dragover="onDragOver" @dragleave="onDragLeave" @drop="onDrop">
|
<div ref="content" class="p-fileupload-content" @dragenter="onDragEnter" @dragover="onDragOver" @dragleave="onDragLeave" @drop="onDrop">
|
||||||
<FileUploadProgressBar v-if="hasFiles" :value="progress" />
|
<slot name="content" :files="files" :uploadedFiles="uploadedFiles" :removeUploadedFileCallback="removeUploadedFile" :removeFileCallback="remove" :progress="progress" :messages="messages">
|
||||||
<FileUploadMessage v-for="msg of messages" :key="msg" severity="error" @close="onMessageClose">{{ msg }}</FileUploadMessage>
|
<FileUploadProgressBar v-if="hasFiles" :value="progress" :showValue="false" />
|
||||||
<div v-if="hasFiles" class="p-fileupload-files">
|
<FileUploadMessage v-for="msg of messages" :key="msg" severity="error" @close="onMessageClose">{{ msg }}</FileUploadMessage>
|
||||||
<div v-for="(file, index) of files" :key="file.name + file.type + file.size" class="p-fileupload-row">
|
<FileContent v-if="hasFiles" :files="files" @remove="remove" :badgeValue="pendingLabel" :previewWidth="previewWidth" />
|
||||||
<div>
|
<FileContent :files="uploadedFiles" @remove="removeUploadedFile" :badgeValue="completedLabel" badgeSeverity="success" :previewWidth="previewWidth" />
|
||||||
<img v-if="isImage(file)" role="presentation" :alt="file.name" :src="file.objectURL" :width="previewWidth" />
|
</slot>
|
||||||
</div>
|
<div v-if="$slots.empty && !hasFiles && !hasUploadedFiles" class="p-fileupload-empty">
|
||||||
<div class="p-fileupload-filename">{{ file.name }}</div>
|
|
||||||
<div>{{ formatSize(file.size) }}</div>
|
|
||||||
<div>
|
|
||||||
<FileUploadButton type="button" icon="pi pi-times" @click="remove(index)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="$slots.empty && !hasFiles" class="p-fileupload-empty">
|
|
||||||
<slot name="empty"></slot>
|
<slot name="empty"></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,14 +35,15 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Button from 'primevue/button';
|
import Button from 'primevue/button';
|
||||||
import ProgressBar from 'primevue/progressbar';
|
|
||||||
import Message from 'primevue/message';
|
import Message from 'primevue/message';
|
||||||
import { DomHandler } from 'primevue/utils';
|
import ProgressBar from 'primevue/progressbar';
|
||||||
import Ripple from 'primevue/ripple';
|
import Ripple from 'primevue/ripple';
|
||||||
|
import { DomHandler } from 'primevue/utils';
|
||||||
|
import FileContent from './FileContent.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FileUpload',
|
name: 'FileUpload',
|
||||||
emits: ['select', 'uploader', 'before-upload', 'progress', 'upload', 'error', 'before-send', 'clear', 'remove'],
|
emits: ['select', 'uploader', 'before-upload', 'progress', 'upload', 'error', 'before-send', 'clear', 'remove', 'remove-uploaded-file'],
|
||||||
props: {
|
props: {
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -152,7 +147,8 @@ export default {
|
||||||
files: [],
|
files: [],
|
||||||
messages: [],
|
messages: [],
|
||||||
focused: false,
|
focused: false,
|
||||||
progress: null
|
progress: null,
|
||||||
|
uploadedFiles: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -250,6 +246,7 @@ export default {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.uploadedFiles.push(...this.files);
|
||||||
this.clear();
|
this.clear();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -379,6 +376,15 @@ export default {
|
||||||
files: this.files
|
files: this.files
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
removeUploadedFile(index) {
|
||||||
|
let removedFile = this.uploadedFiles.splice(index, 1)[0];
|
||||||
|
|
||||||
|
this.uploadedFiles = [...this.uploadedFiles];
|
||||||
|
this.$emit('remove-uploaded-file', {
|
||||||
|
file: removedFile,
|
||||||
|
files: this.uploadedFiles
|
||||||
|
});
|
||||||
|
},
|
||||||
clearInputElement() {
|
clearInputElement() {
|
||||||
this.$refs.fileInput.value = '';
|
this.$refs.fileInput.value = '';
|
||||||
},
|
},
|
||||||
|
@ -456,6 +462,9 @@ export default {
|
||||||
hasFiles() {
|
hasFiles() {
|
||||||
return this.files && this.files.length > 0;
|
return this.files && this.files.length > 0;
|
||||||
},
|
},
|
||||||
|
hasUploadedFiles() {
|
||||||
|
return this.uploadedFiles && this.uploadedFiles.length > 0;
|
||||||
|
},
|
||||||
chooseDisabled() {
|
chooseDisabled() {
|
||||||
return this.disabled || (this.fileLimit && this.fileLimit <= this.files.length + this.uploadedFileCount);
|
return this.disabled || (this.fileLimit && this.fileLimit <= this.files.length + this.uploadedFileCount);
|
||||||
},
|
},
|
||||||
|
@ -473,12 +482,19 @@ export default {
|
||||||
},
|
},
|
||||||
cancelButtonLabel() {
|
cancelButtonLabel() {
|
||||||
return this.cancelLabel || this.$primevue.config.locale.cancel;
|
return this.cancelLabel || this.$primevue.config.locale.cancel;
|
||||||
|
},
|
||||||
|
completedLabel() {
|
||||||
|
return this.$primevue.config.locale.completed;
|
||||||
|
},
|
||||||
|
pendingLabel() {
|
||||||
|
return this.$primevue.config.locale.pending;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
FileUploadButton: Button,
|
FileUploadButton: Button,
|
||||||
FileUploadProgressBar: ProgressBar,
|
FileUploadProgressBar: ProgressBar,
|
||||||
FileUploadMessage: Message
|
FileUploadMessage: Message,
|
||||||
|
FileContent
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
ripple: Ripple
|
ripple: Ripple
|
||||||
|
@ -491,20 +507,6 @@ export default {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-fileupload-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-fileupload-row > div {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-fileupload-row > div:last-child {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-fileupload-content .p-progressbar {
|
.p-fileupload-content .p-progressbar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -517,19 +519,31 @@ export default {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-button.p-fileupload-choose input[type='file'] {
|
.p-fileupload-buttonbar {
|
||||||
display: none;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-fileupload-choose.p-fileupload-choose-selected input[type='file'] {
|
.p-fileupload > input[type='file'],
|
||||||
|
.p-fileupload-basic input[type='file'] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-fileupload-filename {
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-fluid .p-fileupload .p-button {
|
.p-fluid .p-fileupload .p-button {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-fileupload-file {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-fileupload-file-thumbnail {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-fileupload-file-actions {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { ObjectDirective } from 'vue';
|
||||||
|
|
||||||
|
declare const Ripple: ObjectDirective;
|
||||||
|
|
||||||
|
export default Ripple;
|
|
@ -0,0 +1,114 @@
|
||||||
|
import { DomHandler, ObjectUtils } from 'primevue/utils';
|
||||||
|
|
||||||
|
function bind(el, binding) {
|
||||||
|
const { onFocusIn, onFocusOut } = binding.value || {};
|
||||||
|
|
||||||
|
el.$_pfocustrap_mutationobserver = new MutationObserver((mutationList) => {
|
||||||
|
mutationList.forEach((mutation) => {
|
||||||
|
if (mutation.type === 'childList' && !el.contains(document.activeElement)) {
|
||||||
|
const findNextFocusableElement = (el) => {
|
||||||
|
const focusableElement = DomHandler.isFocusableElement(el) ? el : DomHandler.getFirstFocusableElement(el);
|
||||||
|
|
||||||
|
return ObjectUtils.isNotEmpty(focusableElement) ? focusableElement : findNextFocusableElement(el.nextSibling);
|
||||||
|
};
|
||||||
|
|
||||||
|
DomHandler.focus(findNextFocusableElement(mutation.nextSibling));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
el.$_pfocustrap_mutationobserver.disconnect();
|
||||||
|
el.$_pfocustrap_mutationobserver.observe(el, {
|
||||||
|
childList: true
|
||||||
|
});
|
||||||
|
|
||||||
|
el.$_pfocustrap_focusinlistener = (event) => onFocusIn && onFocusIn(event);
|
||||||
|
el.$_pfocustrap_focusoutlistener = (event) => onFocusOut && onFocusOut(event);
|
||||||
|
|
||||||
|
el.addEventListener('focusin', el.$_pfocustrap_focusinlistener);
|
||||||
|
el.addEventListener('focusout', el.$_pfocustrap_focusoutlistener);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unbind(el) {
|
||||||
|
el.$_pfocustrap_mutationobserver && el.$_pfocustrap_mutationobserver.disconnect();
|
||||||
|
el.$_pfocustrap_focusinlistener && el.removeEventListener('focusin', el.$_pfocustrap_focusinlistener) && (el.$_pfocustrap_focusinlistener = null);
|
||||||
|
el.$_pfocustrap_focusoutlistener && el.removeEventListener('focusout', el.$_pfocustrap_focusoutlistener) && (el.$_pfocustrap_focusoutlistener = null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function autoFocus(el, binding) {
|
||||||
|
const { autoFocusSelector = '', firstFocusableSelector = '', autoFocus = false } = binding.value || {};
|
||||||
|
let focusableElement = DomHandler.getFirstFocusableElement(el, `[autofocus]:not(.p-hidden-focusable)${autoFocusSelector}`);
|
||||||
|
|
||||||
|
autoFocus && !focusableElement && (focusableElement = DomHandler.getFirstFocusableElement(el, `:not(.p-hidden-focusable)${firstFocusableSelector}`));
|
||||||
|
DomHandler.focus(focusableElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFirstHiddenElementFocus(event) {
|
||||||
|
const { currentTarget, relatedTarget } = event;
|
||||||
|
const focusableElement =
|
||||||
|
relatedTarget === currentTarget.$_pfocustrap_lasthiddenfocusableelement
|
||||||
|
? DomHandler.getFirstFocusableElement(currentTarget.parentElement, `:not(.p-hidden-focusable)${currentTarget.$_pfocustrap_focusableselector}`)
|
||||||
|
: currentTarget.$_pfocustrap_lasthiddenfocusableelement;
|
||||||
|
|
||||||
|
DomHandler.focus(focusableElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLastHiddenElementFocus(event) {
|
||||||
|
const { currentTarget, relatedTarget } = event;
|
||||||
|
const focusableElement =
|
||||||
|
relatedTarget === currentTarget.$_pfocustrap_firsthiddenfocusableelement
|
||||||
|
? DomHandler.getLastFocusableElement(currentTarget.parentElement, `:not(.p-hidden-focusable)${currentTarget.$_pfocustrap_focusableselector}`)
|
||||||
|
: currentTarget.$_pfocustrap_firsthiddenfocusableelement;
|
||||||
|
|
||||||
|
DomHandler.focus(focusableElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createHiddenFocusableElements(el, binding) {
|
||||||
|
const { tabIndex = 0, firstFocusableSelector = '', lastFocusableSelector = '' } = binding.value || {};
|
||||||
|
|
||||||
|
const createFocusableElement = (onFocus) => {
|
||||||
|
const element = document.createElement('span');
|
||||||
|
|
||||||
|
element.classList = 'p-hidden-accessible p-hidden-focusable';
|
||||||
|
element.tabIndex = tabIndex;
|
||||||
|
element.setAttribute('aria-hidden', 'true');
|
||||||
|
element.setAttribute('role', 'presentation');
|
||||||
|
element.addEventListener('focus', onFocus);
|
||||||
|
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
|
||||||
|
const firstFocusableElement = createFocusableElement(onFirstHiddenElementFocus);
|
||||||
|
const lastFocusableElement = createFocusableElement(onLastHiddenElementFocus);
|
||||||
|
|
||||||
|
firstFocusableElement.$_pfocustrap_lasthiddenfocusableelement = lastFocusableElement;
|
||||||
|
firstFocusableElement.$_pfocustrap_focusableselector = firstFocusableSelector;
|
||||||
|
|
||||||
|
lastFocusableElement.$_pfocustrap_firsthiddenfocusableelement = firstFocusableElement;
|
||||||
|
lastFocusableElement.$_pfocustrap_focusableselector = lastFocusableSelector;
|
||||||
|
|
||||||
|
el.prepend(firstFocusableElement);
|
||||||
|
el.append(lastFocusableElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FocusTrap = {
|
||||||
|
mounted(el, binding) {
|
||||||
|
const { disabled } = binding.value || {};
|
||||||
|
|
||||||
|
if (!disabled) {
|
||||||
|
createHiddenFocusableElements(el, binding);
|
||||||
|
bind(el, binding);
|
||||||
|
autoFocus(el, binding);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updated(el, binding) {
|
||||||
|
const { disabled } = binding.value || {};
|
||||||
|
|
||||||
|
disabled && unbind(el);
|
||||||
|
},
|
||||||
|
unmounted(el) {
|
||||||
|
unbind(el);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FocusTrap;
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"main": "./focustrap.cjs.js",
|
||||||
|
"module": "./focustrap.esm.js",
|
||||||
|
"unpkg": "./focustrap.min.js",
|
||||||
|
"types": "./FocusTrap.d.ts"
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { VNode } from 'vue';
|
import { ButtonHTMLAttributes, HTMLAttributes, VNode } from 'vue';
|
||||||
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
||||||
|
|
||||||
type GalleriaThumbnailsPositionType = 'bottom' | 'top' | 'left' | 'right' | undefined;
|
type GalleriaThumbnailsPositionType = 'bottom' | 'top' | 'left' | 'right' | undefined;
|
||||||
|
@ -119,11 +119,23 @@ export interface GalleriaProps {
|
||||||
/**
|
/**
|
||||||
* Inline style of the component on fullscreen mode. Otherwise, the 'style' property can be used.
|
* Inline style of the component on fullscreen mode. Otherwise, the 'style' property can be used.
|
||||||
*/
|
*/
|
||||||
containerStyle?: any;
|
containerStyle?: any | undefined;
|
||||||
/**
|
/**
|
||||||
* Style class of the component on fullscreen mode. Otherwise, the 'class' property can be used.
|
* Style class of the component on fullscreen mode. Otherwise, the 'class' property can be used.
|
||||||
*/
|
*/
|
||||||
containerClass?: any;
|
containerClass?: any | undefined;
|
||||||
|
/**
|
||||||
|
* Uses to pass all properties of the HTMLDivElement to the container element on fullscreen mode.
|
||||||
|
*/
|
||||||
|
containerProps?: HTMLAttributes | undefined;
|
||||||
|
/**
|
||||||
|
* Uses to pass all properties of the HTMLButtonElement to the previous navigation button.
|
||||||
|
*/
|
||||||
|
prevButtonProps?: ButtonHTMLAttributes | undefined;
|
||||||
|
/**
|
||||||
|
* Uses to pass all properties of the HTMLButtonElement to the next navigation button.
|
||||||
|
*/
|
||||||
|
nextButtonProps?: ButtonHTMLAttributes | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GalleriaSlots {
|
export interface GalleriaSlots {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import PrimeVue from '../config/PrimeVue';
|
import PrimeVue from 'primevue/config';
|
||||||
import Galleria from './Galleria.vue';
|
import Galleria from './Galleria.vue';
|
||||||
|
|
||||||
describe('Gallleria.vue', () => {
|
describe('Gallleria.vue', () => {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<Portal v-if="fullScreen">
|
<Portal v-if="fullScreen">
|
||||||
<div v-if="containerVisible" :ref="maskRef" :class="maskContentClass">
|
<div v-if="containerVisible" :ref="maskRef" :class="maskContentClass" :role="fullScreen ? 'dialog' : 'region'" :aria-modal="fullScreen ? 'true' : undefined">
|
||||||
<transition name="p-galleria" @before-enter="onBeforeEnter" @enter="onEnter" @before-leave="onBeforeLeave" @after-leave="onAfterLeave" appear>
|
<transition name="p-galleria" @before-enter="onBeforeEnter" @enter="onEnter" @before-leave="onBeforeLeave" @after-leave="onAfterLeave" appear>
|
||||||
<GalleriaContent v-if="visible" :ref="containerRef" v-bind="$props" @mask-hide="maskHide" :templates="$slots" @activeitem-change="onActiveItemChange" />
|
<GalleriaContent v-if="visible" :ref="containerRef" v-focustrap v-bind="$props" @mask-hide="maskHide" :templates="$slots" @activeitem-change="onActiveItemChange" />
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</Portal>
|
</Portal>
|
||||||
|
@ -10,9 +10,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import GalleriaContent from './GalleriaContent.vue';
|
import FocusTrap from 'primevue/focustrap';
|
||||||
import { DomHandler, ZIndexUtils } from 'primevue/utils';
|
|
||||||
import Portal from 'primevue/portal';
|
import Portal from 'primevue/portal';
|
||||||
|
import { DomHandler, ZIndexUtils } from 'primevue/utils';
|
||||||
|
import GalleriaContent from './GalleriaContent.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Galleria',
|
name: 'Galleria',
|
||||||
|
@ -107,8 +108,26 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
containerStyle: null,
|
containerStyle: {
|
||||||
containerClass: null
|
type: null,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
containerClass: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
containerProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
prevButtonProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
nextButtonProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
container: null,
|
container: null,
|
||||||
mask: null,
|
mask: null,
|
||||||
|
@ -141,6 +160,7 @@ export default {
|
||||||
onEnter(el) {
|
onEnter(el) {
|
||||||
this.mask.style.zIndex = String(parseInt(el.style.zIndex, 10) - 1);
|
this.mask.style.zIndex = String(parseInt(el.style.zIndex, 10) - 1);
|
||||||
DomHandler.addClass(document.body, 'p-overflow-hidden');
|
DomHandler.addClass(document.body, 'p-overflow-hidden');
|
||||||
|
this.focus();
|
||||||
},
|
},
|
||||||
onBeforeLeave() {
|
onBeforeLeave() {
|
||||||
DomHandler.addClass(this.mask, 'p-component-overlay-leave');
|
DomHandler.addClass(this.mask, 'p-component-overlay-leave');
|
||||||
|
@ -163,6 +183,13 @@ export default {
|
||||||
},
|
},
|
||||||
maskRef(el) {
|
maskRef(el) {
|
||||||
this.mask = el;
|
this.mask = el;
|
||||||
|
},
|
||||||
|
focus() {
|
||||||
|
let focusTarget = this.container.$el.querySelector('[autofocus]');
|
||||||
|
|
||||||
|
if (focusTarget) {
|
||||||
|
focusTarget.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -180,6 +207,9 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
GalleriaContent: GalleriaContent,
|
GalleriaContent: GalleriaContent,
|
||||||
Portal: Portal
|
Portal: Portal
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
focustrap: FocusTrap
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="$attrs.value && $attrs.value.length > 0" :id="id" :class="galleriaClass" :style="$attrs.containerStyle">
|
<div v-if="$attrs.value && $attrs.value.length > 0" :id="id" :class="galleriaClass" :style="$attrs.containerStyle" v-bind="$attrs.containerProps">
|
||||||
<button v-if="$attrs.fullScreen" v-ripple type="button" class="p-galleria-close p-link" @click="$emit('mask-hide')">
|
<button v-if="$attrs.fullScreen" v-ripple autofocus type="button" class="p-galleria-close p-link" :aria-label="closeAriaLabel" @click="$emit('mask-hide')">
|
||||||
<span class="p-galleria-close-icon pi pi-times"></span>
|
<span class="p-galleria-close-icon pi pi-times"></span>
|
||||||
</button>
|
</button>
|
||||||
<div v-if="$attrs.templates && $attrs.templates['header']" class="p-galleria-header">
|
<div v-if="$attrs.templates && $attrs.templates['header']" class="p-galleria-header">
|
||||||
<component :is="$attrs.templates['header']" />
|
<component :is="$attrs.templates['header']" />
|
||||||
</div>
|
</div>
|
||||||
<div class="p-galleria-content">
|
<div class="p-galleria-content" :aria-live="$attrs.autoPlay ? 'polite' : 'off'">
|
||||||
<GalleriaItem
|
<GalleriaItem
|
||||||
|
:id="id"
|
||||||
v-model:activeIndex="activeIndex"
|
v-model:activeIndex="activeIndex"
|
||||||
v-model:slideShowActive="slideShowActive"
|
v-model:slideShowActive="slideShowActive"
|
||||||
:value="$attrs.value"
|
:value="$attrs.value"
|
||||||
|
@ -34,6 +35,8 @@
|
||||||
:isVertical="isVertical()"
|
:isVertical="isVertical()"
|
||||||
:contentHeight="$attrs.verticalThumbnailViewPortHeight"
|
:contentHeight="$attrs.verticalThumbnailViewPortHeight"
|
||||||
:showThumbnailNavigators="$attrs.showThumbnailNavigators"
|
:showThumbnailNavigators="$attrs.showThumbnailNavigators"
|
||||||
|
:prevButtonProps="$attrs.prevButtonProps"
|
||||||
|
:nextButtonProps="$attrs.nextButtonProps"
|
||||||
@stop-slideshow="stopSlideShow"
|
@stop-slideshow="stopSlideShow"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -44,11 +47,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Ripple from 'primevue/ripple';
|
||||||
import { UniqueComponentId } from 'primevue/utils';
|
import { UniqueComponentId } from 'primevue/utils';
|
||||||
import GalleriaItem from './GalleriaItem.vue';
|
import GalleriaItem from './GalleriaItem.vue';
|
||||||
import GalleriaThumbnails from './GalleriaThumbnails.vue';
|
import GalleriaThumbnails from './GalleriaThumbnails.vue';
|
||||||
import GalleriaItemSlot from './GalleriaItemSlot.vue';
|
|
||||||
import Ripple from 'primevue/ripple';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'GalleriaContent',
|
name: 'GalleriaContent',
|
||||||
|
@ -130,12 +132,14 @@ export default {
|
||||||
indicatorPosClass,
|
indicatorPosClass,
|
||||||
this.$attrs.containerClass
|
this.$attrs.containerClass
|
||||||
];
|
];
|
||||||
|
},
|
||||||
|
closeAriaLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.close : undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
GalleriaItem: GalleriaItem,
|
GalleriaItem: GalleriaItem,
|
||||||
GalleriaThumbnails: GalleriaThumbnails,
|
GalleriaThumbnails: GalleriaThumbnails
|
||||||
GalleriaItemSlot: GalleriaItemSlot
|
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
ripple: Ripple
|
ripple: Ripple
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<button v-if="showItemNavigators" v-ripple type="button" :class="navBackwardClass" @click="navBackward($event)" :disabled="isNavBackwardDisabled()">
|
<button v-if="showItemNavigators" v-ripple type="button" :class="navBackwardClass" @click="navBackward($event)" :disabled="isNavBackwardDisabled()">
|
||||||
<span class="p-galleria-item-prev-icon pi pi-chevron-left"></span>
|
<span class="p-galleria-item-prev-icon pi pi-chevron-left"></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="p-galleria-item">
|
<div :id="id + '_item_' + activeIndex" class="p-galleria-item" role="group" :aria-label="ariaSlideNumber(activeIndex + 1)" :aria-roledescription="ariaSlideLabel">
|
||||||
<component v-if="templates.item" :is="templates.item" :item="activeItem" />
|
<component v-if="templates.item" :is="templates.item" :item="activeItem" />
|
||||||
</div>
|
</div>
|
||||||
<button v-if="showItemNavigators" v-ripple type="button" :class="navForwardClass" @click="navForward($event)" :disabled="isNavForwardDisabled()">
|
<button v-if="showItemNavigators" v-ripple type="button" :class="navForwardClass" @click="navForward($event)" :disabled="isNavForwardDisabled()">
|
||||||
|
@ -18,11 +18,14 @@
|
||||||
<li
|
<li
|
||||||
v-for="(item, index) of value"
|
v-for="(item, index) of value"
|
||||||
:key="`p-galleria-indicator-${index}`"
|
:key="`p-galleria-indicator-${index}`"
|
||||||
|
:class="['p-galleria-indicator', { 'p-highlight': isIndicatorItemActive(index) }]"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
:aria-label="ariaPageLabel(index + 1)"
|
||||||
|
:aria-selected="activeIndex === index"
|
||||||
|
:aria-controls="id + '_item_' + index"
|
||||||
@click="onIndicatorClick(index)"
|
@click="onIndicatorClick(index)"
|
||||||
@mouseenter="onIndicatorMouseEnter(index)"
|
@mouseenter="onIndicatorMouseEnter(index)"
|
||||||
@keydown.enter="onIndicatorKeyDown(index)"
|
@keydown="onIndicatorKeyDown($event, index)"
|
||||||
:class="['p-galleria-indicator', { 'p-highlight': isIndicatorItemActive(index) }]"
|
|
||||||
>
|
>
|
||||||
<button v-if="!templates['indicator']" type="button" tabindex="-1" class="p-link"></button>
|
<button v-if="!templates['indicator']" type="button" tabindex="-1" class="p-link"></button>
|
||||||
<component v-if="templates.indicator" :is="templates.indicator" :index="index" />
|
<component v-if="templates.indicator" :is="templates.indicator" :index="index" />
|
||||||
|
@ -73,6 +76,10 @@ export default {
|
||||||
templates: {
|
templates: {
|
||||||
type: null,
|
type: null,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -125,10 +132,24 @@ export default {
|
||||||
this.$emit('update:activeIndex', index);
|
this.$emit('update:activeIndex', index);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onIndicatorKeyDown(index) {
|
onIndicatorKeyDown(event, index) {
|
||||||
this.stopSlideShow();
|
switch (event.code) {
|
||||||
|
case 'Enter':
|
||||||
|
case 'Space':
|
||||||
|
this.stopSlideShow();
|
||||||
|
|
||||||
this.$emit('update:activeIndex', index);
|
this.$emit('update:activeIndex', index);
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ArrowDown':
|
||||||
|
case 'ArrowUp':
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
isIndicatorItemActive(index) {
|
isIndicatorItemActive(index) {
|
||||||
return this.activeIndex === index;
|
return this.activeIndex === index;
|
||||||
|
@ -138,6 +159,12 @@ export default {
|
||||||
},
|
},
|
||||||
isNavForwardDisabled() {
|
isNavForwardDisabled() {
|
||||||
return !this.circular && this.activeIndex === this.value.length - 1;
|
return !this.circular && this.activeIndex === this.value.length - 1;
|
||||||
|
},
|
||||||
|
ariaSlideNumber(value) {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.slideNumber.replace(/{slideNumber}/g, value) : undefined;
|
||||||
|
},
|
||||||
|
ariaPageLabel(value) {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.pageLabel.replace(/{page}/g, value) : undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -159,6 +186,9 @@ export default {
|
||||||
'p-disabled': this.isNavForwardDisabled()
|
'p-disabled': this.isNavForwardDisabled()
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
},
|
||||||
|
ariaSlideLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.slide : undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
functional: true,
|
|
||||||
props: {
|
|
||||||
item: {
|
|
||||||
type: null,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
index: {
|
|
||||||
type: Number,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
templates: {
|
|
||||||
type: null,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
default: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render(createElement, context) {
|
|
||||||
const { item, index, templates, type } = context.props;
|
|
||||||
const template = templates && templates[type];
|
|
||||||
|
|
||||||
if (template) {
|
|
||||||
let content;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'item':
|
|
||||||
case 'caption':
|
|
||||||
case 'thumbnail':
|
|
||||||
content = template({
|
|
||||||
item
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'indicator':
|
|
||||||
content = template({
|
|
||||||
index
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
content = template({});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return content ? [content] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
|
@ -1,11 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="p-galleria-thumbnail-wrapper">
|
<div class="p-galleria-thumbnail-wrapper">
|
||||||
<div class="p-galleria-thumbnail-container">
|
<div class="p-galleria-thumbnail-container">
|
||||||
<button v-if="showThumbnailNavigators" v-ripple :class="navBackwardClass" @click="navBackward($event)" :disabled="isNavBackwardDisabled()" type="button">
|
<button v-if="showThumbnailNavigators" v-ripple :class="navBackwardClass" :disabled="isNavBackwardDisabled()" type="button" :aria-label="ariaPrevButtonLabel" @click="navBackward($event)" v-bind="prevButtonProps">
|
||||||
<span :class="navBackwardIconClass"></span>
|
<span :class="navBackwardIconClass"></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="p-galleria-thumbnail-items-container" :style="{ height: isVertical ? contentHeight : '' }">
|
<div class="p-galleria-thumbnail-items-container" :style="{ height: isVertical ? contentHeight : '' }">
|
||||||
<div ref="itemsContainer" class="p-galleria-thumbnail-items" @transitionend="onTransitionEnd" @touchstart="onTouchStart($event)" @touchmove="onTouchMove($event)" @touchend="onTouchEnd($event)">
|
<div ref="itemsContainer" class="p-galleria-thumbnail-items" role="tablist" @transitionend="onTransitionEnd" @touchstart="onTouchStart($event)" @touchmove="onTouchMove($event)" @touchend="onTouchEnd($event)">
|
||||||
<div
|
<div
|
||||||
v-for="(item, index) of value"
|
v-for="(item, index) of value"
|
||||||
:key="`p-galleria-thumbnail-item-${index}`"
|
:key="`p-galleria-thumbnail-item-${index}`"
|
||||||
|
@ -18,14 +18,18 @@
|
||||||
'p-galleria-thumbnail-item-end': lastItemActiveIndex() === index
|
'p-galleria-thumbnail-item-end': lastItemActiveIndex() === index
|
||||||
}
|
}
|
||||||
]"
|
]"
|
||||||
|
role="tab"
|
||||||
|
:aria-selected="activeIndex === index"
|
||||||
|
:aria-controls="containerId + '_item_' + index"
|
||||||
|
@keydown="onThumbnailKeydown($event, index)"
|
||||||
>
|
>
|
||||||
<div class="p-galleria-thumbnail-item-content" :tabindex="isItemActive(index) ? 0 : null" @click="onItemClick(index)" @keydown.enter="onItemClick(index)">
|
<div class="p-galleria-thumbnail-item-content" :tabindex="activeIndex === index ? '0' : '-1'" :aria-label="ariaPageLabel(index + 1)" :aria-current="activeIndex === index ? 'page' : undefined" @click="onItemClick(index)">
|
||||||
<component v-if="templates.thumbnail" :is="templates.thumbnail" :item="item" />
|
<component v-if="templates.thumbnail" :is="templates.thumbnail" :item="item" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button v-if="showThumbnailNavigators" v-ripple :class="navForwardClass" @click="navForward($event)" :disabled="isNavForwardDisabled()" type="button">
|
<button v-if="showThumbnailNavigators" v-ripple :class="navForwardClass" :disabled="isNavForwardDisabled()" type="button" :aria-label="ariaNextButtonLabel" @click="navForward($event)" v-bind="nextButtonProps">
|
||||||
<span :class="navForwardIconClass"></span>
|
<span :class="navForwardIconClass"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -33,8 +37,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { DomHandler } from 'primevue/utils';
|
|
||||||
import Ripple from 'primevue/ripple';
|
import Ripple from 'primevue/ripple';
|
||||||
|
import { DomHandler } from 'primevue/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'GalleriaThumbnails',
|
name: 'GalleriaThumbnails',
|
||||||
|
@ -83,6 +87,14 @@ export default {
|
||||||
templates: {
|
templates: {
|
||||||
type: null,
|
type: null,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
prevButtonProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
nextButtonProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
startPos: null,
|
startPos: null,
|
||||||
|
@ -211,7 +223,7 @@ export default {
|
||||||
navForward(e) {
|
navForward(e) {
|
||||||
this.stopSlideShow();
|
this.stopSlideShow();
|
||||||
|
|
||||||
let nextItemIndex = this.d_activeIndex + 1;
|
let nextItemIndex = this.d_activeIndex === this.value.length - 1 ? this.value.length - 1 : this.d_activeIndex + 1;
|
||||||
|
|
||||||
if (nextItemIndex + this.totalShiftedItems > this.getMedianItemIndex() && (-1 * this.totalShiftedItems < this.getTotalPageNumber() - 1 || this.circular)) {
|
if (nextItemIndex + this.totalShiftedItems > this.getMedianItemIndex() && (-1 * this.totalShiftedItems < this.getTotalPageNumber() - 1 || this.circular)) {
|
||||||
this.step(-1);
|
this.step(-1);
|
||||||
|
@ -251,6 +263,89 @@ export default {
|
||||||
this.$emit('update:activeIndex', selectedItemIndex);
|
this.$emit('update:activeIndex', selectedItemIndex);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onThumbnailKeydown(event, index) {
|
||||||
|
if (event.code === 'Enter' || event.code === 'Space') {
|
||||||
|
this.onItemClick(index);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (event.code) {
|
||||||
|
case 'ArrowRight':
|
||||||
|
this.onRightKey();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ArrowLeft':
|
||||||
|
this.onLeftKey();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Home':
|
||||||
|
this.onHomeKey();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'End':
|
||||||
|
this.onEndKey();
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ArrowUp':
|
||||||
|
case 'ArrowDown':
|
||||||
|
event.preventDefault();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Tab':
|
||||||
|
this.onTabKey();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRightKey() {
|
||||||
|
const indicators = DomHandler.find(this.$refs.itemsContainer, '.p-galleria-thumbnail-item');
|
||||||
|
const activeIndex = this.findFocusedIndicatorIndex();
|
||||||
|
|
||||||
|
this.changedFocusedIndicator(activeIndex, activeIndex + 1 === indicators.length ? indicators.length - 1 : activeIndex + 1);
|
||||||
|
},
|
||||||
|
onLeftKey() {
|
||||||
|
const activeIndex = this.findFocusedIndicatorIndex();
|
||||||
|
|
||||||
|
this.changedFocusedIndicator(activeIndex, activeIndex - 1 <= 0 ? 0 : activeIndex - 1);
|
||||||
|
},
|
||||||
|
onHomeKey() {
|
||||||
|
const activeIndex = this.findFocusedIndicatorIndex();
|
||||||
|
|
||||||
|
this.changedFocusedIndicator(activeIndex, 0);
|
||||||
|
},
|
||||||
|
onEndKey() {
|
||||||
|
const indicators = DomHandler.find(this.$refs.itemsContainer, '.p-galleria-thumbnail-item');
|
||||||
|
const activeIndex = this.findFocusedIndicatorIndex();
|
||||||
|
|
||||||
|
this.changedFocusedIndicator(activeIndex, indicators.length - 1);
|
||||||
|
},
|
||||||
|
onTabKey() {
|
||||||
|
const indicators = [...DomHandler.find(this.$refs.itemsContainer, '.p-galleria-thumbnail-item')];
|
||||||
|
const highlightedIndex = indicators.findIndex((ind) => DomHandler.hasClass(ind, 'p-galleria-thumbnail-item-current'));
|
||||||
|
|
||||||
|
const activeIndicator = DomHandler.findSingle(this.$refs.itemsContainer, '.p-galleria-thumbnail-item > [tabindex="0"');
|
||||||
|
const activeIndex = indicators.findIndex((ind) => ind === activeIndicator.parentElement);
|
||||||
|
|
||||||
|
indicators[activeIndex].children[0].tabIndex = '-1';
|
||||||
|
indicators[highlightedIndex].children[0].tabIndex = '0';
|
||||||
|
},
|
||||||
|
findFocusedIndicatorIndex() {
|
||||||
|
const indicators = [...DomHandler.find(this.$refs.itemsContainer, '.p-galleria-thumbnail-item')];
|
||||||
|
const activeIndicator = DomHandler.findSingle(this.$refs.itemsContainer, '.p-galleria-thumbnail-item > [tabindex="0"]');
|
||||||
|
|
||||||
|
return indicators.findIndex((ind) => ind === activeIndicator.parentElement);
|
||||||
|
},
|
||||||
|
changedFocusedIndicator(prevInd, nextInd) {
|
||||||
|
const indicators = DomHandler.find(this.$refs.itemsContainer, '.p-galleria-thumbnail-item');
|
||||||
|
|
||||||
|
indicators[prevInd].children[0].tabIndex = '-1';
|
||||||
|
indicators[nextInd].children[0].tabIndex = '0';
|
||||||
|
indicators[nextInd].children[0].focus();
|
||||||
|
},
|
||||||
onTransitionEnd() {
|
onTransitionEnd() {
|
||||||
if (this.$refs.itemsContainer) {
|
if (this.$refs.itemsContainer) {
|
||||||
DomHandler.addClass(this.$refs.itemsContainer, 'p-items-hidden');
|
DomHandler.addClass(this.$refs.itemsContainer, 'p-items-hidden');
|
||||||
|
@ -384,6 +479,9 @@ export default {
|
||||||
},
|
},
|
||||||
isItemActive(index) {
|
isItemActive(index) {
|
||||||
return this.firstItemAciveIndex() <= index && this.lastItemActiveIndex() >= index;
|
return this.firstItemAciveIndex() <= index && this.lastItemActiveIndex() >= index;
|
||||||
|
},
|
||||||
|
ariaPageLabel(value) {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.pageLabel.replace(/{page}/g, value) : undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -420,6 +518,12 @@ export default {
|
||||||
'pi-chevron-down': this.isVertical
|
'pi-chevron-down': this.isVertical
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
},
|
||||||
|
ariaPrevButtonLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.prevPageLabel : undefined;
|
||||||
|
},
|
||||||
|
ariaNextButtonLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.nextPageLabel : undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
|
import PrimeVue from 'primevue/config';
|
||||||
import Image from './Image.vue';
|
import Image from './Image.vue';
|
||||||
|
|
||||||
describe('Image.vue', () => {
|
describe('Image.vue', () => {
|
||||||
it('should exist', () => {
|
it('should exist', () => {
|
||||||
const wrapper = mount(Image, {
|
const wrapper = mount(Image, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
stubs: {
|
stubs: {
|
||||||
teleport: true
|
teleport: true
|
||||||
}
|
}
|
||||||
|
@ -21,6 +23,7 @@ describe('Image.vue', () => {
|
||||||
it('should preview', async () => {
|
it('should preview', async () => {
|
||||||
const wrapper = mount(Image, {
|
const wrapper = mount(Image, {
|
||||||
global: {
|
global: {
|
||||||
|
plugins: [PrimeVue],
|
||||||
stubs: {
|
stubs: {
|
||||||
teleport: true
|
teleport: true
|
||||||
}
|
}
|
||||||
|
@ -42,6 +45,8 @@ describe('Image.vue', () => {
|
||||||
|
|
||||||
await wrapper.setData({ maskVisible: false });
|
await wrapper.setData({ maskVisible: false });
|
||||||
|
|
||||||
expect(wrapper.find('.p-image-mask').exists()).toBe(false);
|
setTimeout(() => {
|
||||||
|
expect(wrapper.find('.p-image-mask').exists()).toBe(false);
|
||||||
|
}, 25);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
<span :class="containerClass" :style="style">
|
<span :class="containerClass" :style="style">
|
||||||
<img v-bind="$attrs" :style="imageStyle" :class="imageClass" @error="onError" />
|
<img v-bind="$attrs" :style="imageStyle" :class="imageClass" @error="onError" />
|
||||||
<div v-if="preview" class="p-image-preview-indicator" @click="onImageClick">
|
<button v-if="preview" ref="previewButton" class="p-image-preview-indicator" @click="onImageClick" v-bind="previewButtonProps">
|
||||||
<slot name="indicator">
|
<slot name="indicator">
|
||||||
<i class="p-image-preview-icon pi pi-eye"></i>
|
<i class="p-image-preview-icon pi pi-eye"></i>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</button>
|
||||||
<Portal>
|
<Portal>
|
||||||
<div v-if="maskVisible" :ref="maskRef" :class="maskClass" @click="onMaskClick">
|
<div v-if="maskVisible" :ref="maskRef" v-focustrap role="dialog" :class="maskClass" :aria-modal="maskVisible" @click="onMaskClick" @keydown="onMaskKeydown">
|
||||||
<div class="p-image-toolbar">
|
<div class="p-image-toolbar">
|
||||||
<button class="p-image-action p-link" @click="rotateRight" type="button">
|
<button class="p-image-action p-link" @click="rotateRight" type="button" :aria-label="rightAriaLabel">
|
||||||
<i class="pi pi-refresh"></i>
|
<i class="pi pi-refresh"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="p-image-action p-link" @click="rotateLeft" type="button">
|
<button class="p-image-action p-link" @click="rotateLeft" type="button" :aria-label="leftAriaLabel">
|
||||||
<i class="pi pi-undo"></i>
|
<i class="pi pi-undo"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="p-image-action p-link" @click="zoomOut" type="button" :disabled="zoomDisabled">
|
<button class="p-image-action p-link" @click="zoomOut" type="button" :disabled="zoomDisabled" :aria-label="zoomOutAriaLabel">
|
||||||
<i class="pi pi-search-minus"></i>
|
<i class="pi pi-search-minus"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="p-image-action p-link" @click="zoomIn" type="button" :disabled="zoomDisabled">
|
<button class="p-image-action p-link" @click="zoomIn" type="button" :disabled="zoomDisabled" :aria-label="zoomInAriaLabel">
|
||||||
<i class="pi pi-search-plus"></i>
|
<i class="pi pi-search-plus"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="p-image-action p-link" type="button" @click="hidePreview">
|
<button class="p-image-action p-link" type="button" @click="hidePreview" :aria-label="closeAriaLabel" autofocus>
|
||||||
<i class="pi pi-times"></i>
|
<i class="pi pi-times"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,8 +36,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { DomHandler, ZIndexUtils } from 'primevue/utils';
|
import FocusTrap from 'primevue/focustrap';
|
||||||
import Portal from 'primevue/portal';
|
import Portal from 'primevue/portal';
|
||||||
|
import { DomHandler, ZIndexUtils } from 'primevue/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Image',
|
name: 'Image',
|
||||||
|
@ -48,10 +49,26 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
class: null,
|
class: {
|
||||||
style: null,
|
type: null,
|
||||||
imageStyle: null,
|
default: null
|
||||||
imageClass: null
|
},
|
||||||
|
style: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
imageStyle: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
imageClass: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
previewButtonProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mask: null,
|
mask: null,
|
||||||
data() {
|
data() {
|
||||||
|
@ -94,6 +111,21 @@ export default {
|
||||||
|
|
||||||
this.previewClick = false;
|
this.previewClick = false;
|
||||||
},
|
},
|
||||||
|
onMaskKeydown(event) {
|
||||||
|
switch (event.code) {
|
||||||
|
case 'Escape':
|
||||||
|
this.onMaskClick();
|
||||||
|
setTimeout(() => {
|
||||||
|
DomHandler.focus(this.$refs.previewButton);
|
||||||
|
}, 25);
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
onError() {
|
onError() {
|
||||||
this.$emit('error');
|
this.$emit('error');
|
||||||
},
|
},
|
||||||
|
@ -117,6 +149,7 @@ export default {
|
||||||
ZIndexUtils.set('modal', this.mask, this.$primevue.config.zIndex.modal);
|
ZIndexUtils.set('modal', this.mask, this.$primevue.config.zIndex.modal);
|
||||||
},
|
},
|
||||||
onEnter() {
|
onEnter() {
|
||||||
|
this.focus();
|
||||||
this.$emit('show');
|
this.$emit('show');
|
||||||
},
|
},
|
||||||
onBeforeLeave() {
|
onBeforeLeave() {
|
||||||
|
@ -128,6 +161,13 @@ export default {
|
||||||
onAfterLeave(el) {
|
onAfterLeave(el) {
|
||||||
ZIndexUtils.clear(el);
|
ZIndexUtils.clear(el);
|
||||||
this.maskVisible = false;
|
this.maskVisible = false;
|
||||||
|
},
|
||||||
|
focus() {
|
||||||
|
let focusTarget = this.mask.querySelector('[autofocus]');
|
||||||
|
|
||||||
|
if (focusTarget) {
|
||||||
|
focusTarget.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -151,10 +191,28 @@ export default {
|
||||||
},
|
},
|
||||||
zoomDisabled() {
|
zoomDisabled() {
|
||||||
return this.scale <= 0.5 || this.scale >= 1.5;
|
return this.scale <= 0.5 || this.scale >= 1.5;
|
||||||
|
},
|
||||||
|
rightAriaLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.rotateRight : undefined;
|
||||||
|
},
|
||||||
|
leftAriaLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.rotateLeft : undefined;
|
||||||
|
},
|
||||||
|
zoomInAriaLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.zoomIn : undefined;
|
||||||
|
},
|
||||||
|
zoomOutAriaLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.zoomOut : undefined;
|
||||||
|
},
|
||||||
|
closeAriaLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.close : undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Portal: Portal
|
Portal: Portal
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
focustrap: FocusTrap
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { VNode } from 'vue';
|
import { HTMLAttributes, ButtonHTMLAttributes, VNode } from 'vue';
|
||||||
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
||||||
|
|
||||||
export interface InplaceProps {
|
export interface InplaceProps {
|
||||||
|
@ -14,6 +14,19 @@ export interface InplaceProps {
|
||||||
* When present, it specifies that the element should be disabled.
|
* When present, it specifies that the element should be disabled.
|
||||||
*/
|
*/
|
||||||
disabled?: boolean | undefined;
|
disabled?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to display in the close button.
|
||||||
|
* Default value is 'pi pi-times'.
|
||||||
|
*/
|
||||||
|
closeIcon?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Uses to pass all properties of the HTMLDivElement to display container.
|
||||||
|
*/
|
||||||
|
displayProps?: HTMLAttributes | undefined;
|
||||||
|
/**
|
||||||
|
* Uses to pass all properties of the HTMLButtonElement to the close button.
|
||||||
|
*/
|
||||||
|
closeButtonProps?: ButtonHTMLAttributes | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InplaceSlots {
|
export interface InplaceSlots {
|
||||||
|
|
|
@ -1,6 +1,18 @@
|
||||||
import { mount } from '@vue/test-utils';
|
import { config, mount } from '@vue/test-utils';
|
||||||
import Inplace from './Inplace.vue';
|
import Inplace from './Inplace.vue';
|
||||||
import InputText from '../inputtext/InputText.vue';
|
import InputText from '@/components/inputtext/InputText.vue';
|
||||||
|
|
||||||
|
config.global.mocks = {
|
||||||
|
$primevue: {
|
||||||
|
config: {
|
||||||
|
locale: {
|
||||||
|
aria: {
|
||||||
|
close: 'Close'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
describe('Inplace.vue', () => {
|
describe('Inplace.vue', () => {
|
||||||
it('should exist', () => {
|
it('should exist', () => {
|
||||||
|
@ -64,4 +76,26 @@ describe('Inplace.vue', () => {
|
||||||
|
|
||||||
expect(wrapper.find('.pi.pi-times').exists()).toBe(false);
|
expect(wrapper.find('.pi.pi-times').exists()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should have custom close icon', async () => {
|
||||||
|
const wrapper = mount(Inplace, {
|
||||||
|
global: {
|
||||||
|
components: {
|
||||||
|
InputText
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
closable: true,
|
||||||
|
closeIcon: 'pi pi-discord'
|
||||||
|
},
|
||||||
|
slots: {
|
||||||
|
display: `{{'Click to Edit'}}`,
|
||||||
|
content: `<InputText autoFocus />`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await wrapper.vm.open({});
|
||||||
|
|
||||||
|
expect(wrapper.find('.pi.pi-discord').exists()).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="containerClass">
|
<div :class="containerClass" aria-live="polite">
|
||||||
<div v-if="!d_active" :class="displayClass" :tabindex="$attrs.tabindex || '0'" @click="open" @keydown.enter="open">
|
<div v-if="!d_active" ref="display" :class="displayClass" :tabindex="$attrs.tabindex || '0'" role="button" @click="open" @keydown.enter="open" v-bind="displayProps">
|
||||||
<slot name="display"></slot>
|
<slot name="display"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="p-inplace-content">
|
<div v-else class="p-inplace-content">
|
||||||
<slot name="content"></slot>
|
<slot name="content"></slot>
|
||||||
<IPButton v-if="closable" icon="pi pi-times" @click="close"></IPButton>
|
<IPButton v-if="closable" :icon="closeIcon" :aria-label="closeAriaLabel" @click="close" v-bind="closeButtonProps" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -28,6 +28,18 @@ export default {
|
||||||
disabled: {
|
disabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
closeIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'pi pi-times'
|
||||||
|
},
|
||||||
|
displayProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
closeButtonProps: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -54,6 +66,9 @@ export default {
|
||||||
this.$emit('close', event);
|
this.$emit('close', event);
|
||||||
this.d_active = false;
|
this.d_active = false;
|
||||||
this.$emit('update:active', false);
|
this.$emit('update:active', false);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$refs.display.focus();
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -62,6 +77,9 @@ export default {
|
||||||
},
|
},
|
||||||
displayClass() {
|
displayClass() {
|
||||||
return ['p-inplace-display', { 'p-disabled': this.disabled }];
|
return ['p-inplace-display', { 'p-disabled': this.disabled }];
|
||||||
|
},
|
||||||
|
closeAriaLabel() {
|
||||||
|
return this.$primevue.config.locale.aria ? this.$primevue.config.locale.aria.close : undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -134,6 +134,11 @@ export interface InputNumberProps {
|
||||||
* Default value is true.
|
* Default value is true.
|
||||||
*/
|
*/
|
||||||
allowEmpty?: boolean | undefined;
|
allowEmpty?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Highlights automatically the input value.
|
||||||
|
* Default value is false.
|
||||||
|
*/
|
||||||
|
highlightOnFocus?: boolean | undefined;
|
||||||
/**
|
/**
|
||||||
* When present, it specifies that the component should be disabled.
|
* When present, it specifies that the component should be disabled.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -18,24 +18,24 @@ describe('InputNumber.vue', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('is keydown called when down and up keys pressed', async () => {
|
it('is keydown called when down and up keys pressed', async () => {
|
||||||
await wrapper.vm.onInputKeyDown({ which: 38, target: { value: 1 }, preventDefault: () => {} });
|
await wrapper.vm.onInputKeyDown({ code: 'ArrowUp', target: { value: 1 }, preventDefault: () => {} });
|
||||||
|
|
||||||
expect(wrapper.emitted()['update:modelValue'][0]).toEqual([2]);
|
expect(wrapper.emitted()['update:modelValue'][0]).toEqual([2]);
|
||||||
|
|
||||||
await wrapper.vm.onInputKeyDown({ which: 40, target: { value: 2 }, preventDefault: () => {} });
|
await wrapper.vm.onInputKeyDown({ code: 'ArrowDown', target: { value: 2 }, preventDefault: () => {} });
|
||||||
|
|
||||||
expect(wrapper.emitted()['update:modelValue'][1]).toEqual([1]);
|
expect(wrapper.emitted()['update:modelValue'][1]).toEqual([1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('is keydown called when tab key pressed', async () => {
|
it('is keydown called when tab key pressed', async () => {
|
||||||
await wrapper.vm.onInputKeyDown({ which: 9, target: { value: '12' }, preventDefault: () => {} });
|
await wrapper.vm.onInputKeyDown({ code: 'Tab', target: { value: '12' }, preventDefault: () => {} });
|
||||||
|
|
||||||
expect(wrapper.emitted()['update:modelValue'][0]).toEqual([12]);
|
expect(wrapper.emitted()['update:modelValue'][0]).toEqual([12]);
|
||||||
expect(wrapper.find('input.p-inputnumber-input').attributes()['aria-valuenow']).toBe('12');
|
expect(wrapper.find('input.p-inputnumber-input').attributes()['aria-valuenow']).toBe('12');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('is keydown called when enter key pressed', async () => {
|
it('is keydown called when enter key pressed', async () => {
|
||||||
await wrapper.vm.onInputKeyDown({ which: 13, target: { value: '12' }, preventDefault: () => {} });
|
await wrapper.vm.onInputKeyDown({ code: 'Enter', target: { value: '12' }, preventDefault: () => {} });
|
||||||
|
|
||||||
expect(wrapper.emitted()['update:modelValue'][0]).toEqual([12]);
|
expect(wrapper.emitted()['update:modelValue'][0]).toEqual([12]);
|
||||||
expect(wrapper.find('input.p-inputnumber-input').attributes()['aria-valuenow']).toBe('12');
|
expect(wrapper.find('input.p-inputnumber-input').attributes()['aria-valuenow']).toBe('12');
|
||||||
|
@ -60,11 +60,11 @@ describe('InputNumber.vue', () => {
|
||||||
it('should have min boundary', async () => {
|
it('should have min boundary', async () => {
|
||||||
await wrapper.setProps({ modelValue: 95, min: 95 });
|
await wrapper.setProps({ modelValue: 95, min: 95 });
|
||||||
|
|
||||||
await wrapper.vm.onInputKeyDown({ which: 40, target: { value: 96 }, preventDefault: () => {} });
|
await wrapper.vm.onInputKeyDown({ code: 'ArrowDown', target: { value: 96 }, preventDefault: () => {} });
|
||||||
|
|
||||||
expect(wrapper.emitted()['update:modelValue'][0]).toEqual([95]);
|
expect(wrapper.emitted()['update:modelValue'][0]).toEqual([95]);
|
||||||
|
|
||||||
await wrapper.vm.onInputKeyDown({ which: 40, target: { value: 95 }, preventDefault: () => {} });
|
await wrapper.vm.onInputKeyDown({ code: 'ArrowDown', target: { value: 95 }, preventDefault: () => {} });
|
||||||
|
|
||||||
expect(wrapper.emitted()['update:modelValue'][1]).toEqual([95]);
|
expect(wrapper.emitted()['update:modelValue'][1]).toEqual([95]);
|
||||||
});
|
});
|
||||||
|
@ -72,11 +72,11 @@ describe('InputNumber.vue', () => {
|
||||||
it('should have max boundary', async () => {
|
it('should have max boundary', async () => {
|
||||||
await wrapper.setProps({ modelValue: 99, max: 100 });
|
await wrapper.setProps({ modelValue: 99, max: 100 });
|
||||||
|
|
||||||
await wrapper.vm.onInputKeyDown({ which: 38, target: { value: 99 }, preventDefault: () => {} });
|
await wrapper.vm.onInputKeyDown({ code: 'ArrowUp', target: { value: 99 }, preventDefault: () => {} });
|
||||||
|
|
||||||
expect(wrapper.emitted()['update:modelValue'][0]).toEqual([100]);
|
expect(wrapper.emitted()['update:modelValue'][0]).toEqual([100]);
|
||||||
|
|
||||||
await wrapper.vm.onInputKeyDown({ which: 38, target: { value: 100 }, preventDefault: () => {} });
|
await wrapper.vm.onInputKeyDown({ code: 'ArrowUp', target: { value: 100 }, preventDefault: () => {} });
|
||||||
|
|
||||||
expect(wrapper.emitted()['update:modelValue'][1]).toEqual([100]);
|
expect(wrapper.emitted()['update:modelValue'][1]).toEqual([100]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,8 +35,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import InputText from 'primevue/inputtext';
|
|
||||||
import Button from 'primevue/button';
|
import Button from 'primevue/button';
|
||||||
|
import InputText from 'primevue/inputtext';
|
||||||
|
import { DomHandler } from 'primevue/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'InputNumber',
|
name: 'InputNumber',
|
||||||
|
@ -130,6 +131,10 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
highlightOnFocus: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
readonly: {
|
readonly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
@ -475,46 +480,40 @@ export default {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (event.which) {
|
switch (event.code) {
|
||||||
//up
|
case 'ArrowUp':
|
||||||
case 38:
|
|
||||||
this.spin(event, 1);
|
this.spin(event, 1);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
//down
|
case 'ArrowDown':
|
||||||
case 40:
|
|
||||||
this.spin(event, -1);
|
this.spin(event, -1);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
//left
|
case 'ArrowLeft':
|
||||||
case 37:
|
|
||||||
if (!this.isNumeralChar(inputValue.charAt(selectionStart - 1))) {
|
if (!this.isNumeralChar(inputValue.charAt(selectionStart - 1))) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
//right
|
case 'ArrowRight':
|
||||||
case 39:
|
|
||||||
if (!this.isNumeralChar(inputValue.charAt(selectionStart))) {
|
if (!this.isNumeralChar(inputValue.charAt(selectionStart))) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
//tab and enter
|
case 'Tab':
|
||||||
case 9:
|
case 'Enter':
|
||||||
case 13:
|
|
||||||
newValueStr = this.validateValue(this.parseValue(inputValue));
|
newValueStr = this.validateValue(this.parseValue(inputValue));
|
||||||
this.$refs.input.$el.value = this.formatValue(newValueStr);
|
this.$refs.input.$el.value = this.formatValue(newValueStr);
|
||||||
this.$refs.input.$el.setAttribute('aria-valuenow', newValueStr);
|
this.$refs.input.$el.setAttribute('aria-valuenow', newValueStr);
|
||||||
this.updateModel(event, newValueStr);
|
this.updateModel(event, newValueStr);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
//backspace
|
case 'Backspace': {
|
||||||
case 8: {
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (selectionStart === selectionEnd) {
|
if (selectionStart === selectionEnd) {
|
||||||
|
@ -556,8 +555,7 @@ export default {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// del
|
case 'Delete':
|
||||||
case 46:
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (selectionStart === selectionEnd) {
|
if (selectionStart === selectionEnd) {
|
||||||
|
@ -598,8 +596,7 @@ export default {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
//home
|
case 'Home':
|
||||||
case 36:
|
|
||||||
if (this.min) {
|
if (this.min) {
|
||||||
this.updateModel(event, this.min);
|
this.updateModel(event, this.min);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -607,8 +604,7 @@ export default {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
//end
|
case 'End':
|
||||||
case 35:
|
|
||||||
if (this.max) {
|
if (this.max) {
|
||||||
this.updateModel(event, this.max);
|
this.updateModel(event, this.max);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -836,7 +832,9 @@ export default {
|
||||||
return index || 0;
|
return index || 0;
|
||||||
},
|
},
|
||||||
onInputClick() {
|
onInputClick() {
|
||||||
if (!this.readonly) {
|
const currentValue = this.$refs.input.$el.value;
|
||||||
|
|
||||||
|
if (!this.readonly && currentValue !== DomHandler.getSelection()) {
|
||||||
this.initCursor();
|
this.initCursor();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -869,7 +867,7 @@ export default {
|
||||||
},
|
},
|
||||||
handleOnInput(event, currentValue, newValue) {
|
handleOnInput(event, currentValue, newValue) {
|
||||||
if (this.isValueChanged(currentValue, newValue)) {
|
if (this.isValueChanged(currentValue, newValue)) {
|
||||||
this.$emit('input', { originalEvent: event, value: newValue });
|
this.$emit('input', { originalEvent: event, value: newValue, formattedValue: currentValue });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isValueChanged(currentValue, newValue) {
|
isValueChanged(currentValue, newValue) {
|
||||||
|
@ -978,7 +976,11 @@ export default {
|
||||||
|
|
||||||
this._decimal.lastIndex = 0;
|
this._decimal.lastIndex = 0;
|
||||||
|
|
||||||
return decimalCharIndex !== -1 ? val1.split(this._decimal)[0] + val2.slice(decimalCharIndex) : val1;
|
if (this.suffixChar) {
|
||||||
|
return val1.replace(this.suffixChar, '').split(this._decimal)[0] + val2.replace(this.suffixChar, '').slice(decimalCharIndex) + this.suffixChar;
|
||||||
|
} else {
|
||||||
|
return decimalCharIndex !== -1 ? val1.split(this._decimal)[0] + val2.slice(decimalCharIndex) : val1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return val1;
|
return val1;
|
||||||
|
@ -1000,6 +1002,11 @@ export default {
|
||||||
},
|
},
|
||||||
onInputFocus(event) {
|
onInputFocus(event) {
|
||||||
this.focused = true;
|
this.focused = true;
|
||||||
|
|
||||||
|
if (!this.disabled && !this.readonly && this.$refs.input.$el.value !== DomHandler.getSelection() && this.highlightOnFocus) {
|
||||||
|
event.target.select();
|
||||||
|
}
|
||||||
|
|
||||||
this.$emit('focus', event);
|
this.$emit('focus', event);
|
||||||
},
|
},
|
||||||
onInputBlur(event) {
|
onInputBlur(event) {
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
/* Floating Label */
|
/* Floating Label */
|
||||||
.p-float-label {
|
.p-float-label {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-float-label label {
|
.p-float-label label {
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
.p-float-label textarea ~ label {
|
.p-float-label textarea ~ label {
|
||||||
top: 1rem;
|
top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-float-label input:focus ~ label,
|
.p-float-label input:focus ~ label,
|
||||||
.p-float-label input.p-filled ~ label,
|
.p-float-label input.p-filled ~ label,
|
||||||
.p-float-label textarea:focus ~ label,
|
.p-float-label textarea:focus ~ label,
|
||||||
|
@ -68,6 +68,22 @@
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-float-label .p-placeholder,
|
||||||
|
.p-float-label input::placeholder,
|
||||||
|
.p-float-label .p-inputtext::placeholder {
|
||||||
|
opacity: 0;
|
||||||
|
transition-property: all;
|
||||||
|
transition-timing-function: ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-float-label .p-focus .p-placeholder,
|
||||||
|
.p-float-label input:focus::placeholder,
|
||||||
|
.p-float-label .p-inputtext:focus::placeholder {
|
||||||
|
opacity: 1;
|
||||||
|
transition-property: all;
|
||||||
|
transition-timing-function: ease;
|
||||||
|
}
|
||||||
|
|
||||||
.p-input-icon-left,
|
.p-input-icon-left,
|
||||||
.p-input-icon-right {
|
.p-input-icon-right {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -85,4 +101,4 @@
|
||||||
.p-fluid .p-input-icon-right {
|
.p-fluid .p-input-icon-right {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,11 @@ export interface ListboxProps {
|
||||||
* Index of the element in tabbing order.
|
* Index of the element in tabbing order.
|
||||||
*/
|
*/
|
||||||
tabindex?: number | string | undefined;
|
tabindex?: number | string | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to display in filter input.
|
||||||
|
* Default value is 'pi pi-search'.
|
||||||
|
*/
|
||||||
|
filterIcon?: string | undefined;
|
||||||
/**
|
/**
|
||||||
* Defines a string value that labels an interactive element.
|
* Defines a string value that labels an interactive element.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -14,6 +14,7 @@ config.global.mocks = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Listbox.vue', () => {
|
describe('Listbox.vue', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
|
@ -48,4 +49,17 @@ describe('Listbox.vue', () => {
|
||||||
|
|
||||||
expect(wrapper.findAll('li.p-listbox-item')[0].classes()).toContain('p-highlight');
|
expect(wrapper.findAll('li.p-listbox-item')[0].classes()).toContain('p-highlight');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('filter', () => {
|
||||||
|
it('should have correct custom icon', async () => {
|
||||||
|
await wrapper.setProps({
|
||||||
|
filter: true,
|
||||||
|
filterIcon: 'pi pi-discord'
|
||||||
|
});
|
||||||
|
|
||||||
|
const icon = wrapper.find('.p-listbox-filter-icon');
|
||||||
|
|
||||||
|
expect(icon.classes()).toContain('pi-discord');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
@keydown="onFilterKeyDown"
|
@keydown="onFilterKeyDown"
|
||||||
v-bind="filterInputProps"
|
v-bind="filterInputProps"
|
||||||
/>
|
/>
|
||||||
<span class="p-listbox-filter-icon pi pi-search"></span>
|
<span :class="['p-listbox-filter-icon', filterIcon]" />
|
||||||
</div>
|
</div>
|
||||||
<span role="status" aria-live="polite" class="p-hidden-accessible">
|
<span role="status" aria-live="polite" class="p-hidden-accessible">
|
||||||
{{ filterResultMessageText }}
|
{{ filterResultMessageText }}
|
||||||
|
@ -75,12 +75,6 @@
|
||||||
<slot name="empty">{{ emptyMessageText }}</slot>
|
<slot name="empty">{{ emptyMessageText }}</slot>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<span v-if="!options || (options && options.length === 0)" role="status" aria-live="polite" class="p-hidden-accessible">
|
|
||||||
{{ emptyMessageText }}
|
|
||||||
</span>
|
|
||||||
<span role="status" aria-live="polite" class="p-hidden-accessible">
|
|
||||||
{{ selectedMessageText }}
|
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-if="$slots.loader" v-slot:loader="{ options }">
|
<template v-if="$slots.loader" v-slot:loader="{ options }">
|
||||||
<slot name="loader" :options="options"></slot>
|
<slot name="loader" :options="options"></slot>
|
||||||
|
@ -88,14 +82,20 @@
|
||||||
</VirtualScroller>
|
</VirtualScroller>
|
||||||
</div>
|
</div>
|
||||||
<slot name="footer" :value="modelValue" :options="visibleOptions"></slot>
|
<slot name="footer" :value="modelValue" :options="visibleOptions"></slot>
|
||||||
|
<span v-if="!options || (options && options.length === 0)" role="status" aria-live="polite" class="p-hidden-accessible">
|
||||||
|
{{ emptyMessageText }}
|
||||||
|
</span>
|
||||||
|
<span role="status" aria-live="polite" class="p-hidden-accessible">
|
||||||
|
{{ selectedMessageText }}
|
||||||
|
</span>
|
||||||
<span ref="lastHiddenFocusableElement" role="presentation" aria-hidden="true" class="p-hidden-accessible p-hidden-focusable" :tabindex="!disabled ? tabindex : -1" @focus="onLastHiddenFocus"></span>
|
<span ref="lastHiddenFocusableElement" role="presentation" aria-hidden="true" class="p-hidden-accessible p-hidden-focusable" :tabindex="!disabled ? tabindex : -1" @focus="onLastHiddenFocus"></span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { DomHandler, ObjectUtils, UniqueComponentId } from 'primevue/utils';
|
|
||||||
import { FilterService } from 'primevue/api';
|
import { FilterService } from 'primevue/api';
|
||||||
import Ripple from 'primevue/ripple';
|
import Ripple from 'primevue/ripple';
|
||||||
|
import { DomHandler, ObjectUtils, UniqueComponentId } from 'primevue/utils';
|
||||||
import VirtualScroller from 'primevue/virtualscroller';
|
import VirtualScroller from 'primevue/virtualscroller';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -158,6 +158,10 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
filterIcon: {
|
||||||
|
type: String,
|
||||||
|
default: 'pi pi-search'
|
||||||
|
},
|
||||||
tabindex: {
|
tabindex: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0
|
default: 0
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { VNode } from 'vue';
|
import { VNode } from 'vue';
|
||||||
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
|
||||||
import { MenuItem } from '../menuitem';
|
import { MenuItem } from '../menuitem';
|
||||||
|
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
||||||
|
|
||||||
type MegaMenuOrientationType = 'horizontal' | 'vertical' | undefined;
|
type MegaMenuOrientationType = 'horizontal' | 'vertical' | undefined;
|
||||||
|
|
||||||
|
@ -20,6 +20,22 @@ export interface MegaMenuProps {
|
||||||
* Default value is true.
|
* Default value is true.
|
||||||
*/
|
*/
|
||||||
exact?: boolean | undefined;
|
exact?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* When present, it specifies that the component should be disabled.
|
||||||
|
*/
|
||||||
|
disabled?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Index of the element in tabbing order.
|
||||||
|
*/
|
||||||
|
tabindex?: number | string | undefined;
|
||||||
|
/**
|
||||||
|
* Defines a string value that labels an interactive element.
|
||||||
|
*/
|
||||||
|
'aria-label'?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Identifier of the underlying menu element.
|
||||||
|
*/
|
||||||
|
'aria-labelledby'?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MegaMenuSlots {
|
export interface MegaMenuSlots {
|
||||||
|
@ -43,7 +59,18 @@ export interface MegaMenuSlots {
|
||||||
}) => VNode[];
|
}) => VNode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare type MegaMenuEmits = {};
|
export declare type MegaMenuEmits = {
|
||||||
|
/**
|
||||||
|
* Callback to invoke when the component receives focus.
|
||||||
|
* @param {Event} event - Browser event.
|
||||||
|
*/
|
||||||
|
focus: (event: Event) => void;
|
||||||
|
/**
|
||||||
|
* Callback to invoke when the component loses focus.
|
||||||
|
* @param {Event} event - Browser event.
|
||||||
|
*/
|
||||||
|
blur: (event: Event) => void;
|
||||||
|
};
|
||||||
|
|
||||||
declare class MegaMenu extends ClassComponent<MegaMenuProps, MegaMenuSlots, MegaMenuEmits> {}
|
declare class MegaMenu extends ClassComponent<MegaMenuProps, MegaMenuSlots, MegaMenuEmits> {}
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue