@cloudron/pankow 4.3.6 → 4.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/BottomBar.vue +1 -1
- package/components/BreadCrumb.vue +3 -3
- package/components/Button.vue +6 -0
- package/components/ClipboardAction.vue +1 -1
- package/components/DateTimeInput.vue +2 -1
- package/components/Dialog.vue +4 -2
- package/components/DirectoryView.vue +3 -16
- package/components/DirectoryViewGridItem.vue +8 -71
- package/components/DirectoryViewListItem.vue +6 -64
- package/components/EmailInput.vue +2 -1
- package/components/FileUploader.vue +2 -2
- package/components/MaskedInput.vue +2 -1
- package/components/Menu.vue +1 -1
- package/components/MenuItem.vue +1 -1
- package/components/MenuItemLink.vue +1 -1
- package/components/MultiSelect.vue +1 -1
- package/components/Notification.vue +4 -2
- package/components/NumberInput.vue +2 -1
- package/components/OfflineBanner.vue +1 -1
- package/components/PasswordInput.vue +1 -1
- package/components/Popover.vue +1 -1
- package/components/ProgressBar.vue +1 -1
- package/components/SaveIndicator.vue +1 -1
- package/components/SideBar.vue +2 -2
- package/components/SingleSelect.vue +1 -1
- package/components/Spinner.vue +1 -1
- package/components/Switch.vue +1 -1
- package/components/TabView.vue +42 -6
- package/components/TableView.vue +6 -0
- package/components/TagInput.vue +1 -1
- package/components/TextInput.vue +2 -1
- package/components/TextInputRaw.vue +2 -1
- package/components/TreeView.vue +1 -1
- package/components/TreeViewNode.vue +6 -0
- package/package.json +2 -2
- package/viewers/GenericViewer.vue +1 -1
- package/vite-plugin.js +12 -2
package/components/BottomBar.vue
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<template>
|
|
5
5
|
<div class="pankow-bottom-bar">
|
|
6
6
|
<slot>
|
|
7
|
-
© 2024 <a href="https://cloudron.io" rel="noopener noreferrer" target="_blank">Cloudron</a> <a href="https://forum.cloudron.io" rel="noopener noreferrer" target="_blank">Forum <i class="fa fa-comments"></i></a>
|
|
7
|
+
© 2024 <a href="https://cloudron.io" rel="noopener noreferrer" target="_blank">Cloudron</a> <a href="https://forum.cloudron.io" rel="noopener noreferrer" target="_blank" aria-label="Forum">Forum <i class="fa fa-comments" aria-hidden="true"></i></a>
|
|
8
8
|
</slot>
|
|
9
9
|
</div>
|
|
10
10
|
</template>
|
|
@@ -25,13 +25,13 @@ function onClick() {
|
|
|
25
25
|
</script>
|
|
26
26
|
|
|
27
27
|
<template>
|
|
28
|
-
<div class="pankow-breadcrumb">
|
|
28
|
+
<div class="pankow-breadcrumb" aria-label="Breadcrumb">
|
|
29
29
|
<a class="pankow-breadcrumb-item" :href="home.route" @click="home.action ? home.action(home, $event) : null" @keydown.enter="home.action ? home.action(home, $event) : null" tabindex="0">
|
|
30
30
|
<Icon :class="home.icon"/> {{ home.label }}
|
|
31
31
|
</a>
|
|
32
|
-
<span v-for="item in items">
|
|
32
|
+
<span v-for="(item, index) in items">
|
|
33
33
|
<Icon icon="fa-solid fa-chevron-right" class="pankow-breadcrumb-separator"/>
|
|
34
|
-
<a class="pankow-breadcrumb-item" :href="item.route" @click="item.action ? item.action(home, $event) : null" @keydown.enter="item.action ? item.action(home, $event) : null" tabindex="0">
|
|
34
|
+
<a class="pankow-breadcrumb-item" :href="item.route" @click="item.action ? item.action(home, $event) : null" @keydown.enter="item.action ? item.action(home, $event) : null" tabindex="0" :aria-current="index === items.length - 1 ? 'page' : null">
|
|
35
35
|
<Icon v-if="item.icon" :class="item.icon"/> {{ item.label }}
|
|
36
36
|
</a>
|
|
37
37
|
</span>
|
package/components/Button.vue
CHANGED
|
@@ -53,6 +53,7 @@ function onClick(event) {
|
|
|
53
53
|
<template>
|
|
54
54
|
<component ref="elem" :is="href ? 'a' : 'div'" class="pankow-button" role="button" type="button" :class="{ 'pankow-button-disabled': disabled }"
|
|
55
55
|
@click="onClick" @keydown.enter="onClick" :href="disabled ? null : href" :target="target" tabindex="0"
|
|
56
|
+
:aria-label="!$slots['default'] && icon ? icon : undefined"
|
|
56
57
|
:success="success || undefined" :danger="danger || undefined">
|
|
57
58
|
<Menu ref="menuItem" :model="menu" v-if="menu"></Menu>
|
|
58
59
|
<Spinner v-show="loading" style="stroke: white;" :class="{ 'pankow-button-icon-with-text': $slots['default'] }"/>
|
|
@@ -108,6 +109,11 @@ a.pankow-button:visited {
|
|
|
108
109
|
line-height: inherit;
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
.pankow-button .pankow-spinner {
|
|
113
|
+
width: 1.2em;
|
|
114
|
+
height: 1.2em;
|
|
115
|
+
}
|
|
116
|
+
|
|
111
117
|
.pankow-button-icon-right-with-text {
|
|
112
118
|
padding-left: 7px;
|
|
113
119
|
}
|
|
@@ -19,7 +19,7 @@ function onClipboard() {
|
|
|
19
19
|
</script>
|
|
20
20
|
|
|
21
21
|
<template>
|
|
22
|
-
<i class="pankow-clipboard-action fa-solid fa-copy" @click="onClipboard()"></i>
|
|
22
|
+
<i class="pankow-clipboard-action fa-solid fa-copy" tabindex="0" role="button" aria-label="Copy to clipboard" @click="onClipboard()" @keydown.enter="onClipboard()" @keydown.space.prevent="onClipboard()"></i>
|
|
23
23
|
</template>
|
|
24
24
|
|
|
25
25
|
<style>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const model = defineModel();
|
|
4
4
|
const props = defineProps({
|
|
5
|
+
id: String,
|
|
5
6
|
readonly: {
|
|
6
7
|
type: Boolean,
|
|
7
8
|
default: false
|
|
@@ -15,7 +16,7 @@ const props = defineProps({
|
|
|
15
16
|
</script>
|
|
16
17
|
|
|
17
18
|
<template>
|
|
18
|
-
<input class="pankow-text-input" :type="dateOnly ? 'date' : 'datetime-local'" :readonly="readonly" v-model="model"/>
|
|
19
|
+
<input :id="id" class="pankow-text-input" :type="dateOnly ? 'date' : 'datetime-local'" :readonly="readonly" v-model="model"/>
|
|
19
20
|
</template>
|
|
20
21
|
|
|
21
22
|
<style>
|
package/components/Dialog.vue
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
|
|
3
3
|
import { ref, useTemplateRef, nextTick, computed } from 'vue';
|
|
4
|
+
import { uuidv4 } from '../utils.js';
|
|
4
5
|
import Button from './Button.vue';
|
|
5
6
|
import Icon from './Icon.vue';
|
|
6
7
|
|
|
@@ -60,6 +61,7 @@ const mergedStyle = computed(() => {
|
|
|
60
61
|
})
|
|
61
62
|
|
|
62
63
|
const visible = ref(false);
|
|
64
|
+
const dialogTitleId = ref(uuidv4());
|
|
63
65
|
const dialog = useTemplateRef('dialog');
|
|
64
66
|
const dialogBody = useTemplateRef('dialogBody');
|
|
65
67
|
const confirmButton = useTemplateRef('confirmButton');
|
|
@@ -117,8 +119,8 @@ defineExpose({ open, close });
|
|
|
117
119
|
<div class="pankow-dialog-backdrop" @click="onDismiss" v-show="visible" :style="{ 'z-index': zIndex }"></div>
|
|
118
120
|
</Transition>
|
|
119
121
|
<Transition :name="center ? 'pankow-bounce-center' : 'pankow-bounce-center-top'">
|
|
120
|
-
<div ref="dialog" class="pankow-dialog" v-bind="$attrs" :class="{ 'pankow-dialog-center': center }" v-show="visible" @keydown.esc="onDismiss" tabindex="0" :style="mergedStyle">
|
|
121
|
-
<div class="pankow-dialog-header" v-show="title">
|
|
122
|
+
<div ref="dialog" class="pankow-dialog" role="dialog" aria-modal="true" :aria-labelledby="title ? dialogTitleId : null" v-bind="$attrs" :class="{ 'pankow-dialog-center': center }" v-show="visible" @keydown.esc="onDismiss" tabindex="0" :style="mergedStyle">
|
|
123
|
+
<div :id="dialogTitleId" class="pankow-dialog-header" v-show="title">
|
|
122
124
|
{{ title }}
|
|
123
125
|
</div>
|
|
124
126
|
<div class="pankow-dialog-body" ref="dialogBody" :class="{ 'pankow-dialog-body-no-header': !title, 'pankow-dialog-body-no-buttons': !(rejectLabel || confirmLabel) }">
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
class="directory-view"
|
|
4
4
|
:class="{ 'drop-target-active': dropTargetActive }"
|
|
5
5
|
tabindex="0"
|
|
6
|
+
role="grid"
|
|
6
7
|
ref="main"
|
|
7
8
|
@keydown.up.prevent="onKeyUp($event)"
|
|
8
9
|
@keydown.down.prevent="onKeyDown($event)"
|
|
@@ -56,7 +57,6 @@
|
|
|
56
57
|
:show-size="showSize"
|
|
57
58
|
:show-star="showStar"
|
|
58
59
|
:show-modified="showModified"
|
|
59
|
-
:rename-handler="renameHandler"
|
|
60
60
|
:share-indicator-property="shareIndicatorProperty"
|
|
61
61
|
:file-drop-indicator-property="fileDropIndicatorProperty"
|
|
62
62
|
:select-handler="onSelectAndFocus"
|
|
@@ -76,7 +76,6 @@
|
|
|
76
76
|
:fallbackIcon="fallbackIcon"
|
|
77
77
|
:item="item"
|
|
78
78
|
:show-star="showStar"
|
|
79
|
-
:rename-handler="renameHandler"
|
|
80
79
|
:share-indicator-property="shareIndicatorProperty"
|
|
81
80
|
:file-drop-indicator-property="fileDropIndicatorProperty"
|
|
82
81
|
:select-handler="onSelectAndFocus"
|
|
@@ -262,12 +261,6 @@ const props = defineProps({
|
|
|
262
261
|
console.warn('Missing deleteHandler for DirectoryView');
|
|
263
262
|
},
|
|
264
263
|
},
|
|
265
|
-
renameHandler: {
|
|
266
|
-
type: Function,
|
|
267
|
-
default() {
|
|
268
|
-
console.warn('Missing renameHandler for DirectoryView');
|
|
269
|
-
},
|
|
270
|
-
},
|
|
271
264
|
changeOwnerHandler: {
|
|
272
265
|
type: Function,
|
|
273
266
|
default() {
|
|
@@ -340,7 +333,7 @@ const props = defineProps({
|
|
|
340
333
|
},
|
|
341
334
|
});
|
|
342
335
|
|
|
343
|
-
const emit = defineEmits(['selectionChanged', 'item-activated']);
|
|
336
|
+
const emit = defineEmits(['selectionChanged', 'item-activated', 'rename-requested']);
|
|
344
337
|
|
|
345
338
|
const gridBody = useTemplateRef('gridBody');
|
|
346
339
|
const contextMenu = useTemplateRef('contextMenu');
|
|
@@ -610,12 +603,6 @@ function onItemShare() {
|
|
|
610
603
|
props.shareHandler(focusItem.value);
|
|
611
604
|
}
|
|
612
605
|
|
|
613
|
-
function onItemRenameBegin() {
|
|
614
|
-
if (selectedCount.value !== 1) return console.warn('onItemRenameBegin should only be triggered if one item is selected');
|
|
615
|
-
|
|
616
|
-
elements[focusItem.value.id]?.onRenameBegin();
|
|
617
|
-
}
|
|
618
|
-
|
|
619
606
|
async function onChangeOwnerDialogSubmit(items, newOwnerUid) {
|
|
620
607
|
changeOwnerDialog.busy = true;
|
|
621
608
|
await props.changeOwnerHandler(items, newOwnerUid);
|
|
@@ -844,7 +831,7 @@ const contextMenuModel = ref([
|
|
|
844
831
|
id: 'rename',
|
|
845
832
|
label: props.tr('filemanager.list.menu.rename'),
|
|
846
833
|
icon: 'fa-regular fa-pen-to-square',
|
|
847
|
-
action:
|
|
834
|
+
action: () => emit('rename-requested', focusItem.value),
|
|
848
835
|
visible: () => props.showRename,
|
|
849
836
|
disabled: () => !props.editable || selectedCount.value > 1,
|
|
850
837
|
},
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div ref="rowWrapper" class="grid-item-wrapper"
|
|
3
|
-
:draggable="!rename"
|
|
4
3
|
@mouseup="onSelect($event)"
|
|
5
4
|
@drop="onDrop($event)"
|
|
6
5
|
@dragover="onDragOver($event)"
|
|
@@ -9,15 +8,19 @@
|
|
|
9
8
|
|
|
10
9
|
<div class="grid-item" :class="{ 'focused': item.focused, 'selected': item.selected, 'drop-target-active': dropTargetActive }">
|
|
11
10
|
<div v-if="visible && showStar" class="grid-item-star" @dblclick.stop.prevent @click.stop.prevent="onToggleStar">
|
|
12
|
-
<
|
|
11
|
+
<span tabindex="0" role="button" :aria-label="item.star ? 'Unstar' : 'Star'" @keydown.enter.prevent="onToggleStar" @keydown.space.prevent="onToggleStar">
|
|
12
|
+
<Icon :icon="`${item.star ? 'fa-solid' : 'fa-regular'} fa-star`" class="star-icon" :class="{ 'star-visible': item.star }" />
|
|
13
|
+
</span>
|
|
13
14
|
</div>
|
|
14
15
|
<div v-if="visible" class="grid-item-actions" @mouseup.stop="onActionMenu($event)">
|
|
15
|
-
<
|
|
16
|
+
<span tabindex="0" role="button" aria-label="Actions" @click="onActionMenu($event)" @keydown.enter="onActionMenu($event)" @keydown.space.prevent="onActionMenu($event)">
|
|
17
|
+
<i class="fa-solid fa-ellipsis-vertical"/>
|
|
18
|
+
</span>
|
|
16
19
|
</div>
|
|
17
20
|
|
|
18
21
|
<div class="grid-item-icon">
|
|
19
22
|
<template v-if="visible">
|
|
20
|
-
<img :src="item.previewUrl || item.icon" ref="iconImage" @error="iconError($event)"/>
|
|
23
|
+
<img :src="item.previewUrl || item.icon" ref="iconImage" :alt="item.name" @error="iconError($event)"/>
|
|
21
24
|
<i v-show="shareIndicatorProperty !== '' && !!item[shareIndicatorProperty]" class="fa-solid fa-share-nodes is-shared"></i>
|
|
22
25
|
<i v-show="fileDropIndicatorProperty !== '' && !!item[fileDropIndicatorProperty]" class="fa-solid fa-cloud-arrow-up is-filedrop"></i>
|
|
23
26
|
</template>
|
|
@@ -27,21 +30,10 @@
|
|
|
27
30
|
<div v-if="!visible">
|
|
28
31
|
{{ item.name }}
|
|
29
32
|
</div>
|
|
30
|
-
<div v-else-if="visible
|
|
33
|
+
<div v-else-if="visible" class="grid-item-label" :title="item.name + (item.target ? ` → ${item.target}` : '')">
|
|
31
34
|
<a v-if="item.href" class="open-action" @dblclick.stop @click="onOpen($event)" :href="item.href">{{ item.name }}</a>
|
|
32
35
|
<span v-else>{{ item.name }}</span>
|
|
33
36
|
</div>
|
|
34
|
-
<div v-else-if="visible && rename" ref="renameCell" class="grid-item-label grid-item-rename">
|
|
35
|
-
<TextInput
|
|
36
|
-
v-model="newName"
|
|
37
|
-
:disabled="renameBusy"
|
|
38
|
-
@blur="onRenameEnd"
|
|
39
|
-
@dblclick.stop
|
|
40
|
-
@keydown.enter.stop="onRenameSubmit"
|
|
41
|
-
@keydown.esc.stop="onRenameEnd"
|
|
42
|
-
@keydown.stop
|
|
43
|
-
/>
|
|
44
|
-
</div>
|
|
45
37
|
</div>
|
|
46
38
|
</div>
|
|
47
39
|
</template>
|
|
@@ -51,7 +43,6 @@
|
|
|
51
43
|
import { ref, onMounted } from 'vue';
|
|
52
44
|
|
|
53
45
|
import Icon from './Icon.vue';
|
|
54
|
-
import TextInput from './TextInput.vue';
|
|
55
46
|
|
|
56
47
|
const props = defineProps({
|
|
57
48
|
item: Object,
|
|
@@ -68,12 +59,6 @@ const props = defineProps({
|
|
|
68
59
|
default: '',
|
|
69
60
|
},
|
|
70
61
|
fallbackIcon: String,
|
|
71
|
-
renameHandler: {
|
|
72
|
-
type: Function,
|
|
73
|
-
default() {
|
|
74
|
-
console.warn('Missing renameHandler for DirectoryViewGridItem');
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
62
|
starHandler: {
|
|
78
63
|
type: Function,
|
|
79
64
|
default() {
|
|
@@ -103,12 +88,8 @@ const props = defineProps({
|
|
|
103
88
|
const emit = defineEmits(['activated', 'action-menu']);
|
|
104
89
|
|
|
105
90
|
const rowWrapper = ref(null);
|
|
106
|
-
const renameCell = ref(null);
|
|
107
91
|
|
|
108
92
|
const visible = ref(false);
|
|
109
|
-
const rename = ref(false);
|
|
110
|
-
const renameBusy = ref(false);
|
|
111
|
-
const newName = ref('');
|
|
112
93
|
const dropTargetActive = ref(false);
|
|
113
94
|
const previewRetries = ref(0);
|
|
114
95
|
|
|
@@ -128,37 +109,6 @@ function onActionMenu(event) {
|
|
|
128
109
|
emit('action-menu', props.item, event);
|
|
129
110
|
}
|
|
130
111
|
|
|
131
|
-
function onRenameBegin() {
|
|
132
|
-
rename.value = true;
|
|
133
|
-
newName.value = props.item.name;
|
|
134
|
-
|
|
135
|
-
setTimeout(() => {
|
|
136
|
-
const elem = renameCell.value?.querySelector('input.pankow-text-input');
|
|
137
|
-
if (!elem) return;
|
|
138
|
-
|
|
139
|
-
elem.focus();
|
|
140
|
-
|
|
141
|
-
if (typeof elem.selectionStart !== 'undefined') {
|
|
142
|
-
elem.selectionStart = 0;
|
|
143
|
-
elem.selectionEnd = props.item.name.lastIndexOf('.');
|
|
144
|
-
}
|
|
145
|
-
}, 0);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function onRenameEnd() {
|
|
149
|
-
rename.value = false;
|
|
150
|
-
renameBusy.value = false;
|
|
151
|
-
newName.value = '';
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
async function onRenameSubmit() {
|
|
155
|
-
if (!newName.value) return;
|
|
156
|
-
|
|
157
|
-
renameBusy.value = true;
|
|
158
|
-
await props.renameHandler(props.item, newName.value);
|
|
159
|
-
onRenameEnd();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
112
|
function onOpen(event) {
|
|
163
113
|
if (event.ctrlKey || event.metaKey) return;
|
|
164
114
|
|
|
@@ -228,7 +178,6 @@ onMounted(() => {
|
|
|
228
178
|
|
|
229
179
|
defineExpose({
|
|
230
180
|
highlight,
|
|
231
|
-
onRenameBegin,
|
|
232
181
|
get $el() {
|
|
233
182
|
return rowWrapper.value;
|
|
234
183
|
},
|
|
@@ -346,18 +295,6 @@ defineExpose({
|
|
|
346
295
|
padding: 0 2px;
|
|
347
296
|
}
|
|
348
297
|
|
|
349
|
-
.grid-item-rename {
|
|
350
|
-
overflow: hidden;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
.grid-item-rename :deep(input) {
|
|
354
|
-
width: 100%;
|
|
355
|
-
box-sizing: border-box;
|
|
356
|
-
text-align: center;
|
|
357
|
-
font-size: 0.85rem;
|
|
358
|
-
padding: 1px 4px;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
298
|
.grid-item-star {
|
|
362
299
|
position: absolute;
|
|
363
300
|
top: 4px;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div ref="rowWrapper" class="row-wrapper"
|
|
3
|
-
:draggable="!rename"
|
|
4
3
|
@mouseup="onSelect($event)"
|
|
5
4
|
@drop="onDrop($event)"
|
|
6
5
|
@dragover="onDragOver($event)"
|
|
@@ -12,7 +11,7 @@
|
|
|
12
11
|
<div v-if="!visible" class="col label"></div>
|
|
13
12
|
|
|
14
13
|
<div v-if="visible" class="col icon">
|
|
15
|
-
<img :src="item.previewUrl || item.icon" ref="iconImage" @error="iconError($event)"/>
|
|
14
|
+
<img :src="item.previewUrl || item.icon" ref="iconImage" :alt="item.name" @error="iconError($event)"/>
|
|
16
15
|
<i v-show="shareIndicatorProperty !== '' && !!item[shareIndicatorProperty]" class="fa-solid fa-share-nodes is-shared"></i>
|
|
17
16
|
<i v-show="fileDropIndicatorProperty !== '' && !!item[fileDropIndicatorProperty]" class="fa-solid fa-cloud-arrow-up is-filedrop"></i>
|
|
18
17
|
</div>
|
|
@@ -21,31 +20,21 @@
|
|
|
21
20
|
<div v-if="!visible">
|
|
22
21
|
{{ item.name }}
|
|
23
22
|
</div>
|
|
24
|
-
<div v-else-if="visible
|
|
23
|
+
<div v-else-if="visible" class="col label">
|
|
25
24
|
<a v-show="item.href" class="open-action" @dblclick.stop @click="onOpen($event)" :href="item.href">{{ item.name }} {{ item.target ? `-> ${item.target}` : '' }}</a>
|
|
26
25
|
<span v-show="!item.href">{{ item.name }} {{ item.target ? `→ ${item.target}` : '' }}</span>
|
|
27
26
|
</div>
|
|
28
|
-
<div v-else-if="visible && rename" ref="renameCell" class="col label rename">
|
|
29
|
-
<TextInput
|
|
30
|
-
ref="renameInput"
|
|
31
|
-
v-model="newName"
|
|
32
|
-
:disabled="renameBusy"
|
|
33
|
-
@blur="onRenameEnd"
|
|
34
|
-
@dblclick.stop
|
|
35
|
-
@keydown.enter.stop="onRenameSubmit"
|
|
36
|
-
@keydown.esc.stop="onRenameEnd"
|
|
37
|
-
@keydown.stop
|
|
38
|
-
/>
|
|
39
|
-
</div>
|
|
40
27
|
|
|
41
28
|
<div v-if="visible && showStar" class="col star">
|
|
42
|
-
<
|
|
29
|
+
<span tabindex="0" role="button" :aria-label="item.star ? 'Unstar' : 'Star'" @dblclick.stop.prevent @click.stop.prevent="onToggleStar" @keydown.enter.prevent="onToggleStar" @keydown.space.prevent="onToggleStar">
|
|
30
|
+
<Icon :icon="`${item.star ? 'fa-solid' : 'fa-regular'} fa-star`" class="star-icon" :class="{ 'star-visible': item.star }" />
|
|
31
|
+
</span>
|
|
43
32
|
</div>
|
|
44
33
|
<div v-if="visible && showOwner" class="col owner">{{ item.owner }}</div>
|
|
45
34
|
<div v-if="visible && showSize" class="col size">{{ prettyFileSize(item.size) }}</div>
|
|
46
35
|
<div v-if="visible && showModified" class="col modified" :title="item.modified.toLocaleString()">{{ prettyDate(item.modified) }}</div>
|
|
47
36
|
<div v-if="visible" class="col actions">
|
|
48
|
-
<div @mouseup.stop="onActionMenu($event)"><i class="fa-solid fa-ellipsis-vertical"/></div>
|
|
37
|
+
<div @mouseup.stop="onActionMenu($event)"><span tabindex="0" role="button" aria-label="Actions" @click="onActionMenu($event)" @keydown.enter="onActionMenu($event)" @keydown.space.prevent="onActionMenu($event)"><i class="fa-solid fa-ellipsis-vertical"/></span></div>
|
|
49
38
|
</div>
|
|
50
39
|
</div>
|
|
51
40
|
</div>
|
|
@@ -58,7 +47,6 @@ import { ref, onMounted } from 'vue';
|
|
|
58
47
|
import { prettyDate, prettyFileSize } from '../utils.js';
|
|
59
48
|
|
|
60
49
|
import Icon from './Icon.vue';
|
|
61
|
-
import TextInput from './TextInput.vue';
|
|
62
50
|
|
|
63
51
|
const props = defineProps({
|
|
64
52
|
item: Object,
|
|
@@ -87,12 +75,6 @@ const props = defineProps({
|
|
|
87
75
|
default: '',
|
|
88
76
|
},
|
|
89
77
|
fallbackIcon: String,
|
|
90
|
-
renameHandler: {
|
|
91
|
-
type: Function,
|
|
92
|
-
default() {
|
|
93
|
-
console.warn('Missing renameHandler for DirectoryViewItem');
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
78
|
starHandler: {
|
|
97
79
|
type: Function,
|
|
98
80
|
default() {
|
|
@@ -122,12 +104,8 @@ const props = defineProps({
|
|
|
122
104
|
const emit = defineEmits(['activated', 'action-menu']);
|
|
123
105
|
|
|
124
106
|
const rowWrapper = ref(null);
|
|
125
|
-
const renameCell = ref(null);
|
|
126
107
|
|
|
127
108
|
const visible = ref(false);
|
|
128
|
-
const rename = ref(false);
|
|
129
|
-
const renameBusy = ref(false);
|
|
130
|
-
const newName = ref('');
|
|
131
109
|
const dropTargetActive = ref(false);
|
|
132
110
|
const previewRetries = ref(0);
|
|
133
111
|
|
|
@@ -147,37 +125,6 @@ function onActionMenu(event) {
|
|
|
147
125
|
emit('action-menu', props.item, event);
|
|
148
126
|
}
|
|
149
127
|
|
|
150
|
-
function onRenameBegin() {
|
|
151
|
-
rename.value = true;
|
|
152
|
-
newName.value = props.item.name;
|
|
153
|
-
|
|
154
|
-
setTimeout(() => {
|
|
155
|
-
const elem = renameCell.value?.querySelector('input.pankow-text-input');
|
|
156
|
-
if (!elem) return;
|
|
157
|
-
|
|
158
|
-
elem.focus();
|
|
159
|
-
|
|
160
|
-
if (typeof elem.selectionStart !== 'undefined') {
|
|
161
|
-
elem.selectionStart = 0;
|
|
162
|
-
elem.selectionEnd = props.item.name.lastIndexOf('.');
|
|
163
|
-
}
|
|
164
|
-
}, 0);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function onRenameEnd() {
|
|
168
|
-
rename.value = false;
|
|
169
|
-
renameBusy.value = false;
|
|
170
|
-
newName.value = '';
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
async function onRenameSubmit() {
|
|
174
|
-
if (!newName.value) return;
|
|
175
|
-
|
|
176
|
-
renameBusy.value = true;
|
|
177
|
-
await props.renameHandler(props.item, newName.value);
|
|
178
|
-
onRenameEnd();
|
|
179
|
-
}
|
|
180
|
-
|
|
181
128
|
function onOpen(event) {
|
|
182
129
|
if (event.ctrlKey || event.metaKey) return;
|
|
183
130
|
|
|
@@ -247,7 +194,6 @@ onMounted(() => {
|
|
|
247
194
|
|
|
248
195
|
defineExpose({
|
|
249
196
|
highlight,
|
|
250
|
-
onRenameBegin,
|
|
251
197
|
get $el() {
|
|
252
198
|
return rowWrapper.value;
|
|
253
199
|
},
|
|
@@ -372,10 +318,6 @@ defineExpose({
|
|
|
372
318
|
display: block;
|
|
373
319
|
}
|
|
374
320
|
|
|
375
|
-
.label.rename > button {
|
|
376
|
-
display: block;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
321
|
.size {
|
|
380
322
|
width: 100px;
|
|
381
323
|
margin: auto;
|
|
@@ -4,6 +4,7 @@ import { useTemplateRef } from 'vue';
|
|
|
4
4
|
|
|
5
5
|
const model = defineModel();
|
|
6
6
|
const props = defineProps({
|
|
7
|
+
id: String,
|
|
7
8
|
placeholder: String,
|
|
8
9
|
readonly: {
|
|
9
10
|
type: Boolean,
|
|
@@ -21,7 +22,7 @@ defineExpose({ focus });
|
|
|
21
22
|
</script>
|
|
22
23
|
|
|
23
24
|
<template>
|
|
24
|
-
<input ref="inputElement" class="pankow-text-input" type="email" :placeholder="placeholder" :readonly="readonly" :required="$attrs['required']" v-model.trim="model"/>
|
|
25
|
+
<input :id="id" ref="inputElement" class="pankow-text-input" type="email" :placeholder="placeholder" :readonly="readonly" :required="$attrs['required']" v-model.trim="model"/>
|
|
25
26
|
</template>
|
|
26
27
|
|
|
27
28
|
<style>
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
<Button danger small tool @click="onCancelUpload" icon="fa-solid fa-xmark"></Button>
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
|
-
<input type="file" ref="uploadFileInput" style="display: none" multiple/>
|
|
10
|
-
<input type="file" ref="uploadFolderInput" style="display: none" multiple webkitdirectory directory/>
|
|
9
|
+
<input type="file" ref="uploadFileInput" style="display: none" multiple aria-label="Upload files"/>
|
|
10
|
+
<input type="file" ref="uploadFolderInput" style="display: none" multiple webkitdirectory directory aria-label="Upload folder"/>
|
|
11
11
|
|
|
12
12
|
<InputDialog ref="inputDialog" />
|
|
13
13
|
</div>
|
|
@@ -5,6 +5,7 @@ import { computed } from 'vue';
|
|
|
5
5
|
const model = defineModel({ type: [String, null], default: null });
|
|
6
6
|
|
|
7
7
|
const props = defineProps({
|
|
8
|
+
id: String,
|
|
8
9
|
multiline: {
|
|
9
10
|
type: Boolean,
|
|
10
11
|
default: false
|
|
@@ -27,7 +28,7 @@ const displayValue = computed({
|
|
|
27
28
|
|
|
28
29
|
<template>
|
|
29
30
|
<template v-if="!multiline">
|
|
30
|
-
<input class="pankow-masked-input" :value="displayValue" @input="displayValue = $event.target.value" @focus="$event.target.select()" />
|
|
31
|
+
<input :id="id" class="pankow-masked-input" :value="displayValue" @input="displayValue = $event.target.value" @focus="$event.target.select()" />
|
|
31
32
|
</template>
|
|
32
33
|
<template v-else>
|
|
33
34
|
<textarea :value="displayValue" @input="displayValue = $event.target.value" @focus="$event.target.select()"></textarea>
|
package/components/Menu.vue
CHANGED
|
@@ -325,7 +325,7 @@ defineExpose({
|
|
|
325
325
|
<teleport to="#app">
|
|
326
326
|
<div class="pankow-menu-backdrop" @click="onBackdrop($event)" @contextmenu="onBackdrop($event)" v-show="isOpen"></div>
|
|
327
327
|
<Transition :name="rollUp ? 'pankow-roll-up' : 'pankow-roll-down'">
|
|
328
|
-
<div class="pankow-menu" :class="{ 'pankow-menu-keyboard-nav': keyboardNav }" v-show="isOpen" ref="container" tabindex="0" @keydown.up.stop="selectUp()" @keydown.down.stop="selectDown()" @keydown.esc.stop="close()" @keydown="onKeyDown" @mousemove="onMouseMove">
|
|
328
|
+
<div class="pankow-menu" role="menu" :aria-label="searchThreshold < model.length ? 'Filterable menu' : 'Menu'" :class="{ 'pankow-menu-keyboard-nav': keyboardNav }" v-show="isOpen" ref="container" tabindex="0" @keydown.up.stop="selectUp()" @keydown.down.stop="selectDown()" @keydown.esc.stop="close()" @keydown="onKeyDown" @mousemove="onMouseMove">
|
|
329
329
|
<TextInput placeholder="Filter ..." autocomplete="off" @keydown.up.stop="selectUp()" @keydown.down.stop="selectDown()" @keydown.stop @keydown.esc.stop="close()" @click.stop style="width: 100%; border: 0; padding: 8px 12px;" v-model="searchString" v-if="searchThreshold < model.length"/>
|
|
330
330
|
<component v-for="item in visibleItems" ref="itemElements" :is="item.type || (item.href ? MenuItemLink : MenuItem)" @activated="onItemActivated(item)" :item="item" :has-icons="hasIcons" />
|
|
331
331
|
<MenuItem v-if="model.length === 0" :item="emptyItem"/>
|
package/components/MenuItem.vue
CHANGED
|
@@ -42,7 +42,7 @@ function onActivated() {
|
|
|
42
42
|
</script>
|
|
43
43
|
|
|
44
44
|
<template>
|
|
45
|
-
<div class="pankow-menu-item" :class="{'pankow-menu-item-disabled': isDisabled }" @click.stop="onActivated" :separator="item.separator" v-if="isVisible" tabindex="0" @keydown.enter.stop="onActivated" @keydown.space.stop="onActivated">
|
|
45
|
+
<div class="pankow-menu-item" role="menuitem" :aria-checked="item.checkbox ? item.checked : undefined" :class="{'pankow-menu-item-disabled': isDisabled }" @click.stop="onActivated" :separator="item.separator" v-if="isVisible" tabindex="0" @keydown.enter.stop="onActivated" @keydown.space.stop="onActivated">
|
|
46
46
|
<CheckBox v-model="item.checked" v-if="item.checkbox" style="margin-right: 4px; display: inline-flex;"/>
|
|
47
47
|
<span v-if="!item.separator" :style="{ width: (hasIcons && !item.checkbox) ? '29px' : 0 }"><Icon v-show="hasIcons" :icon="item.icon"/></span>{{ item.label }}
|
|
48
48
|
<div class="pankow-menu-item-separator-line" v-if="item.separator"></div>
|
|
@@ -40,7 +40,7 @@ function onActivated() {
|
|
|
40
40
|
</script>
|
|
41
41
|
|
|
42
42
|
<template>
|
|
43
|
-
<a class="pankow-menu-item" style="display: block" :href="isDisabled ? null : item.href" :target="item.target || null" @click="onActivated" :class="{ 'pankow-menu-item-disabled': isDisabled }" v-if="isVisible">
|
|
43
|
+
<a class="pankow-menu-item" role="menuitem" style="display: block" :href="isDisabled ? null : item.href" :target="item.target || null" @click="onActivated" :class="{ 'pankow-menu-item-disabled': isDisabled }" v-if="isVisible">
|
|
44
44
|
<span :style="{ width: hasIcons ? '29px' : 0 }" style="display: inline-block;"><Icon v-show="hasIcons" :icon="item.icon" /></span>{{ item.label }}
|
|
45
45
|
</a>
|
|
46
46
|
</template>
|
|
@@ -162,7 +162,7 @@ onMounted(() => {
|
|
|
162
162
|
</script>
|
|
163
163
|
|
|
164
164
|
<template>
|
|
165
|
-
<div class="pankow-multiselect" :class="{ 'pankow-multiselect-disabled': disabled }" ref="elem" tabindex="0" @click="onClick" @keydown.enter="onOpen" @keydown.down.stop="onOpen" @keydown.up.stop="onOpen" @keydown.esc="onEscape">
|
|
165
|
+
<div class="pankow-multiselect" role="combobox" :aria-expanded="menu?.isOpen ?? false" aria-haspopup="listbox" :class="{ 'pankow-multiselect-disabled': disabled }" ref="elem" tabindex="0" @click="onClick" @keydown.enter="onOpen" @keydown.down.stop="onOpen" @keydown.up.stop="onOpen" @keydown.esc="onEscape">
|
|
166
166
|
<!-- native select for required and accessibility handling -->
|
|
167
167
|
<select ref="nativeSelect" :required="$attrs['required']" multiple style="display: none">
|
|
168
168
|
<option value=""></option>
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<TransitionGroup name="pankow-notification-fade" :class="`pankow-notification-container ${position}`" tag="div">
|
|
2
|
+
<TransitionGroup name="pankow-notification-fade" :class="`pankow-notification-container ${position}`" tag="div" role="alert" aria-live="polite">
|
|
3
3
|
<div v-for="message in messages" :key="message.id">
|
|
4
4
|
<div class="pankow-notification" :[message.type]="''">
|
|
5
5
|
{{ message.text }}
|
|
6
|
-
<
|
|
6
|
+
<span tabindex="0" role="button" aria-label="Close notification" @keydown.enter="removeNotify(message.id)" @keydown.space.prevent="removeNotify(message.id)" @click="removeNotify(message.id)" class="pankow-notification-close">
|
|
7
|
+
<Icon icon="fa-solid fa-xmark"/>
|
|
8
|
+
</span>
|
|
7
9
|
</div>
|
|
8
10
|
</div>
|
|
9
11
|
</TransitionGroup>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const model = defineModel();
|
|
4
4
|
const props = defineProps({
|
|
5
|
+
id: String,
|
|
5
6
|
placeholder: String,
|
|
6
7
|
readonly: {
|
|
7
8
|
type: Boolean,
|
|
@@ -21,7 +22,7 @@ const props = defineProps({
|
|
|
21
22
|
</script>
|
|
22
23
|
|
|
23
24
|
<template>
|
|
24
|
-
<input class="pankow-text-input" type="number" :step="step" :min="min" :max="max" :placeholder="placeholder" :readonly="readonly" :value="model" @input="$emit('update:modelValue', parseInt($event.target.value))"/>
|
|
25
|
+
<input :id="id" class="pankow-text-input" type="number" :step="step" :min="min" :max="max" :placeholder="placeholder" :readonly="readonly" :value="model" @input="$emit('update:modelValue', parseInt($event.target.value))"/>
|
|
25
26
|
</template>
|
|
26
27
|
|
|
27
28
|
<style>
|
|
@@ -19,7 +19,7 @@ const props = defineProps({
|
|
|
19
19
|
|
|
20
20
|
<template>
|
|
21
21
|
<Transition name="pankow-roll-down">
|
|
22
|
-
<a class="pankow-offline-banner" v-show="active" :href="href" target="_blank">
|
|
22
|
+
<a class="pankow-offline-banner" role="alert" v-show="active" :href="href" target="_blank">
|
|
23
23
|
<i class="fa fa-solid fa-spin fa-circle-notch"/> {{ label }}
|
|
24
24
|
</a>
|
|
25
25
|
</Transition>
|
|
@@ -30,7 +30,7 @@ function toggleReveal() {
|
|
|
30
30
|
<template>
|
|
31
31
|
<div class="pankow-password">
|
|
32
32
|
<input class="pankow-password-input" :id="id" :type="revealed ? 'text' : 'password'" :disabled="disabled ? true : undefined" :placeholder="placeholder" :autofocus="$attrs['autofocus']" :required="$attrs['required']" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)"/>
|
|
33
|
-
<
|
|
33
|
+
<span class="pankow-password-reveal" tabindex="0" role="button" :aria-label="revealed ? 'Hide password' : 'Show password'" @keydown.enter="toggleReveal" @keydown.space.prevent="toggleReveal" @click="toggleReveal" v-html="revealIcon"></span>
|
|
34
34
|
</div>
|
|
35
35
|
</template>
|
|
36
36
|
|
package/components/Popover.vue
CHANGED
|
@@ -144,7 +144,7 @@ defineExpose({
|
|
|
144
144
|
<teleport to="#app">
|
|
145
145
|
<div class="pankow-popover-backdrop" @click="onBackdrop($event)" @contextmenu="onBackdrop($event)" v-show="isOpen"></div>
|
|
146
146
|
<Transition :name="rollUp ? 'pankow-animation-pop-up' : 'pankow-animation-pop-down'">
|
|
147
|
-
<div v-show="isOpen" class="pankow-popover" ref="container" :style="{ 'max-width': width || null, 'max-height': height || null }">
|
|
147
|
+
<div v-show="isOpen" class="pankow-popover" ref="container" role="dialog" aria-label="Popover" :style="{ 'max-width': width || null, 'max-height': height || null }">
|
|
148
148
|
<slot></slot>
|
|
149
149
|
</div>
|
|
150
150
|
</Transition>
|
|
@@ -35,7 +35,7 @@ const normalizedValue = computed(() => {
|
|
|
35
35
|
</script>
|
|
36
36
|
|
|
37
37
|
<template>
|
|
38
|
-
<div class="pankow-progress-bar">
|
|
38
|
+
<div class="pankow-progress-bar" role="progressbar" :aria-valuenow="normalizedValue" aria-valuemin="0" aria-valuemax="100" :aria-label="'Progress: ' + normalizedValue + '%'">
|
|
39
39
|
<div class="pankow-progress-bar-label" v-show="showLabel"><slot>{{ value + ' %' }}</slot></div>
|
|
40
40
|
<div :class="{ 'pankow-progress-bar-background': showTrack }">
|
|
41
41
|
<div v-if="mode !== 'indeterminate'" class="pankow-progress-bar-filled" :style="{ width: normalizedValue+'%', height: slim ? '2px' : '6px' }">
|
|
@@ -42,7 +42,7 @@ defineExpose({ success: showSuccess, error: showError });
|
|
|
42
42
|
<div class="pankow-save-indicator-wrapper">
|
|
43
43
|
<slot />
|
|
44
44
|
<Transition name="pankow-save-indicator-bounce">
|
|
45
|
-
<div class="pankow-save-indicator" v-if="visible" :class="{ success: success, error: !success }"><Icon :icon="success ? 'fa-solid fa-check' : 'fa-solid fa-xmark'"/></div>
|
|
45
|
+
<div class="pankow-save-indicator" role="status" aria-live="polite" v-if="visible" :class="{ success: success, error: !success }"><Icon :icon="success ? 'fa-solid fa-check' : 'fa-solid fa-xmark'"/></div>
|
|
46
46
|
</Transition>
|
|
47
47
|
</div>
|
|
48
48
|
</template>
|
package/components/SideBar.vue
CHANGED
|
@@ -38,8 +38,8 @@ defineExpose({ open, close });
|
|
|
38
38
|
</Transition>
|
|
39
39
|
<div class="pankow-sidebar" ref="sideBar" :class="{ 'pankow-sidebar-closed': !isVisible }">
|
|
40
40
|
<Transition name="pankow-scale">
|
|
41
|
-
<div class="pankow-sidebar-close-action" v-if="isVisible" @click="close()"><i class="fa-solid fa-xmark"></i></div>
|
|
42
|
-
<div class="pankow-sidebar-open-action" v-else-if="showOpenAction" @click="open()"><i class="fa-solid fa-bars"></i></div>
|
|
41
|
+
<div class="pankow-sidebar-close-action" v-if="isVisible" tabindex="0" role="button" aria-label="Close sidebar" @click="close()" @keydown.enter="close()" @keydown.space.prevent="close()"><i class="fa-solid fa-xmark"></i></div>
|
|
42
|
+
<div class="pankow-sidebar-open-action" v-else-if="showOpenAction" tabindex="0" role="button" aria-label="Open sidebar" @click="open()" @keydown.enter="open()" @keydown.space.prevent="open()"><i class="fa-solid fa-bars"></i></div>
|
|
43
43
|
</Transition>
|
|
44
44
|
<div class="pankow-sidebar-inner">
|
|
45
45
|
<slot></slot>
|
|
@@ -176,7 +176,7 @@ watchEffect(handleDefaultSelect);
|
|
|
176
176
|
</script>
|
|
177
177
|
|
|
178
178
|
<template>
|
|
179
|
-
<div class="pankow-singleselect" :class="{ 'pankow-singleselect-disabled': disabled }" ref="elem" tabindex="0" @click="onClick" @keydown.enter="onOpen" @keydown.down.stop.prevent="onSelectNext" @keydown.up.stop.prevent="onSelectPrev" @keydown="onKeyDown($event)">
|
|
179
|
+
<div class="pankow-singleselect" role="combobox" :aria-expanded="menu?.isOpen ?? false" aria-haspopup="listbox" :class="{ 'pankow-singleselect-disabled': disabled }" ref="elem" tabindex="0" @click="onClick" @keydown.enter="onOpen" @keydown.down.stop.prevent="onSelectNext" @keydown.up.stop.prevent="onSelectPrev" @keydown="onKeyDown($event)">
|
|
180
180
|
<!-- native select for required and accessibility handling -->
|
|
181
181
|
<select v-model="selectedKey" :id="id" ref="nativeSelect" :required="$attrs['required']" style="display: none">
|
|
182
182
|
<option value=""></option>
|
package/components/Spinner.vue
CHANGED
package/components/Switch.vue
CHANGED
|
@@ -22,7 +22,7 @@ function onChange(event) {
|
|
|
22
22
|
|
|
23
23
|
<template>
|
|
24
24
|
<label :for="id || internalId" class="pankow-switch" :class="{ 'pankow-switch-disabled': disabled }">
|
|
25
|
-
<input :id="id || internalId" class="pankow-switch-input" type="checkbox" v-model="model" :disabled="disabled" @change="onChange"/>
|
|
25
|
+
<input :id="id || internalId" class="pankow-switch-input" type="checkbox" role="switch" :aria-checked="model" v-model="model" :disabled="disabled" @change="onChange"/>
|
|
26
26
|
<span></span>
|
|
27
27
|
<div class="pankow-switch-input-label">
|
|
28
28
|
<slot>{{ label }}</slot>
|
package/components/TabView.vue
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="pankow-tabview" :top="tabPosition === 'top' ? true : null" :left="tabPosition === 'left' ? true : null" :right="tabPosition === 'right' ? true : null" :bottom="tabPosition === 'bottom' ? true : null">
|
|
3
|
-
<div class="pankow-tabview-tabs" v-if="tabPosition === 'top' || tabPosition === 'left'">
|
|
4
|
-
<div class="pankow-tabview-tab" :class="{'active': active === tab }" v-for="tab in Object.keys(tabs)" @click="open(tab)">{{ tabs[tab] }}</div>
|
|
3
|
+
<div class="pankow-tabview-tabs" role="tablist" :aria-label="tabPosition === 'top' || tabPosition === 'bottom' ? 'Tabs' : 'Tabs'" v-if="tabPosition === 'top' || tabPosition === 'left'">
|
|
4
|
+
<div class="pankow-tabview-tab" role="tab" :tabindex="active === tab ? 0 : -1" :aria-selected="active === tab ? 'true' : 'false'" :aria-controls="'tabpanel-'+tab" :class="{'active': active === tab }" v-for="tab in Object.keys(tabs)" @click="open(tab)" @keydown="onTabKeydown($event, tab)">{{ tabs[tab] }}</div>
|
|
5
5
|
</div>
|
|
6
6
|
<div class="pankow-tabview-content">
|
|
7
|
-
<div v-for="tab in Object.keys(tabs)">
|
|
7
|
+
<div v-for="tab in Object.keys(tabs)" :id="'tabpanel-'+tab" role="tabpanel" :aria-labelledby="tab" tabindex="0">
|
|
8
8
|
<slot :name="tab" v-if="active === tab"/>
|
|
9
9
|
</div>
|
|
10
10
|
</div>
|
|
11
|
-
<div class="pankow-tabview-tabs" v-if="tabPosition === 'bottom' || tabPosition === 'right'">
|
|
12
|
-
<div class="pankow-tabview-tab" :class="{'active': active === tab }" v-for="tab in Object.keys(tabs)" @click="open(tab)">{{ tabs[tab] }}</div>
|
|
11
|
+
<div class="pankow-tabview-tabs" role="tablist" :aria-label="tabPosition === 'top' || tabPosition === 'bottom' ? 'Tabs' : 'Tabs'" v-if="tabPosition === 'bottom' || tabPosition === 'right'">
|
|
12
|
+
<div class="pankow-tabview-tab" role="tab" :tabindex="active === tab ? 0 : -1" :aria-selected="active === tab ? 'true' : 'false'" :aria-controls="'tabpanel-'+tab" :class="{'active': active === tab }" v-for="tab in Object.keys(tabs)" @click="open(tab)" @keydown="onTabKeydown($event, tab)">{{ tabs[tab] }}</div>
|
|
13
13
|
</div>
|
|
14
14
|
</div>
|
|
15
15
|
</template>
|
|
@@ -43,6 +43,42 @@ function open(key) {
|
|
|
43
43
|
emit('changed', key);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function onTabKeydown(event, currentKey) {
|
|
47
|
+
const keys = Object.keys(props.tabs);
|
|
48
|
+
const index = keys.indexOf(currentKey);
|
|
49
|
+
const isVertical = props.tabPosition === 'left' || props.tabPosition === 'right';
|
|
50
|
+
|
|
51
|
+
if (isVertical) {
|
|
52
|
+
if (event.key === 'ArrowDown') {
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
const next = index + 1 >= keys.length ? 0 : index + 1;
|
|
55
|
+
open(keys[next]);
|
|
56
|
+
} else if (event.key === 'ArrowUp') {
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
const prev = index <= 0 ? keys.length - 1 : index - 1;
|
|
59
|
+
open(keys[prev]);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
if (event.key === 'ArrowRight') {
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
const next = index + 1 >= keys.length ? 0 : index + 1;
|
|
65
|
+
open(keys[next]);
|
|
66
|
+
} else if (event.key === 'ArrowLeft') {
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
const prev = index <= 0 ? keys.length - 1 : index - 1;
|
|
69
|
+
open(keys[prev]);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (event.key === 'Home') {
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
open(keys[0]);
|
|
76
|
+
} else if (event.key === 'End') {
|
|
77
|
+
event.preventDefault();
|
|
78
|
+
open(keys[keys.length - 1]);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
46
82
|
</script>
|
|
47
83
|
|
|
48
84
|
<style>
|
|
@@ -153,4 +189,4 @@ function open(key) {
|
|
|
153
189
|
margin-right: 10px;
|
|
154
190
|
}
|
|
155
191
|
|
|
156
|
-
</style>
|
|
192
|
+
</style>
|
package/components/TableView.vue
CHANGED
|
@@ -103,7 +103,13 @@ onMounted(() => {
|
|
|
103
103
|
class="pankow-table-header-column"
|
|
104
104
|
:class="{ 'pankow-table-header-column-sortable': columns[column].sort, 'pankow-table-cell-hide-mobile': columns[column].hideMobile }"
|
|
105
105
|
:style="{ 'text-align': columns[column].align || null, width: typeof columns[column].width === 'string' ? columns[column].width : 'auto' }"
|
|
106
|
+
:aria-sort="sortBy === column ? (sortOrder === SORT_ORDER.ASC ? 'ascending' : 'descending') : 'none'"
|
|
107
|
+
scope="col"
|
|
108
|
+
:tabindex="columns[column].sort ? 0 : null"
|
|
109
|
+
:aria-label="columns[column].sort ? 'Sort by ' + columns[column].label : columns[column].label"
|
|
106
110
|
@click="toggleSort(column)"
|
|
111
|
+
@keydown.enter="toggleSort(column)"
|
|
112
|
+
@keydown.space.prevent="toggleSort(column)"
|
|
107
113
|
>
|
|
108
114
|
<Icon v-if="columns[column].icon" :icon="columns[column].icon" />
|
|
109
115
|
{{ columns[column].label }}
|
package/components/TagInput.vue
CHANGED
|
@@ -41,7 +41,7 @@ function onRemoveIndex(index) {
|
|
|
41
41
|
|
|
42
42
|
<template>
|
|
43
43
|
<div class="pankow-tag-input">
|
|
44
|
-
|
|
44
|
+
<div class="pankow-tag-input-tag" v-for="(tag, index) in model" :key="index">{{ tag }} <span tabindex="0" role="button" :aria-label="'Remove ' + tag" class="pankow-tag-input-tag-remove" @click="onRemoveIndex(index)" @keydown.enter="onRemoveIndex(index)" @keydown.space.prevent="onRemoveIndex(index)"><i class="fa-solid fa-xmark"></i></span></div>
|
|
45
45
|
<input class="pankow-tag-input-input" :id="id" :placeholder="placeholder" :autofocus="$attrs['autofocus']" @keydown="onKey($event)" @blur="onBlur"/>
|
|
46
46
|
</div>
|
|
47
47
|
</template>
|
package/components/TextInput.vue
CHANGED
|
@@ -6,6 +6,7 @@ import { useTemplateRef } from 'vue';
|
|
|
6
6
|
|
|
7
7
|
const model = defineModel();
|
|
8
8
|
const props = defineProps({
|
|
9
|
+
id: String,
|
|
9
10
|
placeholder: String,
|
|
10
11
|
readonly: {
|
|
11
12
|
type: Boolean,
|
|
@@ -23,7 +24,7 @@ defineExpose({ focus });
|
|
|
23
24
|
</script>
|
|
24
25
|
|
|
25
26
|
<template>
|
|
26
|
-
<input ref="inputElement" class="pankow-text-input" type="text" :placeholder="placeholder" :readonly="readonly" v-model.trim="model"/>
|
|
27
|
+
<input :id="id" ref="inputElement" class="pankow-text-input" type="text" :placeholder="placeholder" :readonly="readonly" v-model.trim="model"/>
|
|
27
28
|
</template>
|
|
28
29
|
|
|
29
30
|
<style>
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const model = defineModel();
|
|
6
6
|
const props = defineProps({
|
|
7
|
+
id: String,
|
|
7
8
|
placeholder: String,
|
|
8
9
|
readonly: {
|
|
9
10
|
type: Boolean,
|
|
@@ -14,7 +15,7 @@ const props = defineProps({
|
|
|
14
15
|
</script>
|
|
15
16
|
|
|
16
17
|
<template>
|
|
17
|
-
<input class="pankow-text-input" type="text" :placeholder="placeholder" :readonly="readonly" v-model="model"/>
|
|
18
|
+
<input :id="id" class="pankow-text-input" type="text" :placeholder="placeholder" :readonly="readonly" v-model="model"/>
|
|
18
19
|
</template>
|
|
19
20
|
|
|
20
21
|
<style>
|
package/components/TreeView.vue
CHANGED
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
class="tree-view-node-row"
|
|
5
5
|
:class="{ 'active': isActive, 'drop-target': dropTargetActive }"
|
|
6
6
|
:style="{ paddingLeft: (props.depth * 16 + 4) + 'px' }"
|
|
7
|
+
tabindex="0"
|
|
8
|
+
role="treeitem"
|
|
9
|
+
:aria-expanded="expanded"
|
|
10
|
+
:aria-selected="isActive"
|
|
7
11
|
@click="onRowClick"
|
|
12
|
+
@keydown.enter="onRowClick"
|
|
13
|
+
@keydown.space.prevent="onRowClick"
|
|
8
14
|
@dragover.stop.prevent="onDragOver"
|
|
9
15
|
@dragenter.stop.prevent="onDragEnter"
|
|
10
16
|
@dragleave="onDragLeave"
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudron/pankow",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.4.1",
|
|
5
5
|
"description": "Vue 3 UI component library for Cloudron apps",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"types": "types/index.d.ts",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@highlightjs/vue-plugin": "^2.1.0",
|
|
51
|
-
"@unhead/vue": "^3.2.
|
|
51
|
+
"@unhead/vue": "^3.2.3",
|
|
52
52
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
53
53
|
"highlight.js": "^11.11.1",
|
|
54
54
|
"typescript": "^7.0.2",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<template #body>
|
|
14
14
|
<div class="content">
|
|
15
15
|
<div>
|
|
16
|
-
<img class="preview-icon" :src="entry.previewUrl" />
|
|
16
|
+
<img class="preview-icon" :src="entry.previewUrl" :alt="entry.fileName" />
|
|
17
17
|
<h1>{{ entry.fileName }}</h1>
|
|
18
18
|
<Button icon="fa-solid fa-download" :href="entry.downloadFileUrl">Download</Button>
|
|
19
19
|
</div>
|
package/vite-plugin.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { dirname } from 'node:path';
|
|
1
|
+
import { dirname, resolve } from 'node:path';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
3
4
|
|
|
4
5
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
6
|
|
|
@@ -25,6 +26,15 @@ export default function pankowPlugin() {
|
|
|
25
26
|
if (Array.isArray(allow) && !allow.includes(__dirname)) {
|
|
26
27
|
allow.push(__dirname);
|
|
27
28
|
}
|
|
28
|
-
}
|
|
29
|
+
},
|
|
30
|
+
resolveId(source) {
|
|
31
|
+
if (source.includes('?worker') && source.startsWith('monaco-editor/esm/vs/')) {
|
|
32
|
+
const cleanId = source.replace(/\?worker$/, '');
|
|
33
|
+
const filePath = resolve(__dirname, 'node_modules/', cleanId + '.js');
|
|
34
|
+
if (existsSync(filePath)) {
|
|
35
|
+
return filePath + '?worker';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
29
39
|
};
|
|
30
40
|
}
|