@cloudron/pankow 3.5.9 → 3.5.11

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.
@@ -17,7 +17,7 @@ const internalId = uuidv4();
17
17
 
18
18
  <template>
19
19
  <span class="pankow-checkbox">
20
- <input :id="id || internalId" class="pankow-checkbox-input" type="checkbox" v-model="model" :disabled="disabled"/>
20
+ <input :id="id || internalId" class="pankow-checkbox-input" type="checkbox" v-model="model" :disabled="disabled" @input="$emit('update:modelValue', $event.target.checked)"/>
21
21
  <slot>
22
22
  <label :for="id || internalId" class="pankow-checkbox-input-label">
23
23
  {{ label }}
@@ -1,18 +1,27 @@
1
1
  <script setup>
2
2
 
3
+ import { useTemplateRef } from 'vue';
4
+
3
5
  const model = defineModel();
4
6
  const props = defineProps({
5
7
  placeholder: String,
6
8
  readonly: {
7
9
  type: Boolean,
8
- default: false
10
+ default: false,
9
11
  },
10
12
  });
13
+ const inputElement = useTemplateRef('inputElement');
14
+
15
+ function focus() {
16
+ inputElement.value.focus();
17
+ }
18
+
19
+ defineExpose({ focus });
11
20
 
12
21
  </script>
13
22
 
14
23
  <template>
15
- <input class="pankow-text-input" type="email" :placeholder="placeholder" :readonly="readonly" :value="model" @input="$emit('update:modelValue', $event.target.value)"/>
24
+ <input ref="inputElement" class="pankow-text-input" type="email" :placeholder="placeholder" :readonly="readonly" v-model.trim="model"/>
16
25
  </template>
17
26
 
18
27
  <style>
@@ -1,6 +1,6 @@
1
1
  <template>
2
- <div class="file-uploader" v-show="uploadInProgress">
3
- <div class="label">{{ tr('filemanager.uploader.uploading') }} {{ activeFile ? activeFile.name : '' }} <span style="float: right">{{ prettyUploadSpeed }}</span></div>
2
+ <div class="pankow-file-uploader" v-show="uploadInProgress">
3
+ <div class="pankow-file-uploader-label-and-speed-container"><div class="pankow-file-uploader-label">{{ tr('filemanager.uploader.uploading') }} {{ activeFile ? activeFile.name : '' }}</div><div class="pankow-file-uploader-speed">{{ prettyUploadSpeed }}</div></div>
4
4
  <div style="display: flex;">
5
5
  <ProgressBar :value="parseInt((this.uploadedSize * 100) / this.size) || 0" style="flex-grow: 1"></ProgressBar>
6
6
  <Button danger small tool @click="onCancelUpload" icon="fa-solid fa-xmark"></Button>
@@ -195,10 +195,27 @@ export default {
195
195
 
196
196
  <style>
197
197
 
198
- .file-uploader {
198
+ .pankow-file-uploader {
199
199
  width: 100%;
200
200
  padding: 10px;
201
201
  background-color: var(--pankow-color-background);
202
202
  }
203
203
 
204
+ .pankow-file-uploader-label-and-speed-container {
205
+ display: flex;
206
+ gap: 10px;
207
+ margin-bottom: 5px;
208
+ }
209
+
210
+ .pankow-file-uploader-label {
211
+ flex-grow: 1;
212
+ white-space: nowrap;
213
+ text-overflow: ellipsis;
214
+ overflow: hidden;
215
+ }
216
+
217
+ .pankow-file-uploader-speed {
218
+ white-space: nowrap;
219
+ }
220
+
204
221
  </style>
@@ -90,7 +90,34 @@ const hasIcons = computed(() => {
90
90
  const visibleItems = computed(() => {
91
91
  if (!searchString.value) return props.model;
92
92
  const s = searchString.value.toLowerCase();
93
- return props.model.filter((item) => item.label.toLowerCase().indexOf(s) !== -1);
93
+ let results = [];
94
+
95
+ let group = null;
96
+ let filteredGroupItems = [];
97
+
98
+ for (let item of props.model) {
99
+ if (item.separator) {
100
+ if (group && filteredGroupItems.length) {
101
+ results.push(group);
102
+ results = results.concat(filteredGroupItems);
103
+ }
104
+
105
+ group = item;
106
+ filteredGroupItems = [];
107
+
108
+ continue;
109
+ }
110
+
111
+ if (group && group.label?.toLowerCase().indexOf(s) !== -1) filteredGroupItems.push(item);
112
+ else if (item.label.toLowerCase().indexOf(s) !== -1) filteredGroupItems.push(item);
113
+ }
114
+
115
+ // add remaining group and filtered items (also matches if no group ever set)
116
+ if (group && filteredGroupItems.length) results.push(group);
117
+ if (filteredGroupItems.length) results = results.concat(filteredGroupItems);
118
+
119
+ return results;
120
+ // return props.model.filter((item) => item.label.toLowerCase().indexOf(s) !== -1);
94
121
  });
95
122
 
96
123
  // select menu item by .label
@@ -47,6 +47,7 @@ let selected = ref([]);
47
47
 
48
48
  const elem = useTemplateRef('elem');
49
49
  const menu = useTemplateRef('menu');
50
+ const nativeSelect = useTemplateRef('nativeSelect');
50
51
 
51
52
  watch(model, (newValue, oldValue) => {
52
53
  selected.value = newValue;
@@ -63,7 +64,7 @@ const menuModel = computed(() => {
63
64
  if (item.separator) {
64
65
  return {
65
66
  separator: true,
66
- label: '',
67
+ label: item[props.optionLabel],
67
68
  };
68
69
  } else {
69
70
  return {
@@ -82,6 +83,11 @@ const menuModel = computed(() => {
82
83
 
83
84
  model.value = selected.value;
84
85
  emits('select', selected.value);
86
+
87
+ // let the browser know about the changes
88
+ nativeSelect.value.value = selected.value;
89
+ nativeSelect.value.dispatchEvent(new Event('input', { bubbles: true }));
90
+ nativeSelect.value.dispatchEvent(new Event('change', { bubbles: true }));
85
91
  }
86
92
  };
87
93
  }
@@ -114,11 +120,6 @@ const menuModel = computed(() => {
114
120
  return ret;
115
121
  });
116
122
 
117
- function onMenuClosed() {
118
- elem.value.focus();
119
- emits('close');
120
- }
121
-
122
123
  function onClick(event) {
123
124
  if (props.disabled) return;
124
125
 
@@ -132,9 +133,13 @@ function onOpen(event) {
132
133
  menu.value.open(event, elem.value);
133
134
  }
134
135
 
135
- function onClose(event) {
136
- menu.value.close();
137
- emits('close');
136
+ function onClose(event = null) {
137
+ if (menu.value.isOpen) {
138
+ elem.value.focus();
139
+ if (event) event.stopPropagation();
140
+ menu.value.close();
141
+ emits('close');
142
+ }
138
143
  }
139
144
 
140
145
  onMounted(() => {
@@ -144,14 +149,14 @@ onMounted(() => {
144
149
  </script>
145
150
 
146
151
  <template>
147
- <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.stop="onClose">
152
+ <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="onClose">
148
153
  <!-- native select for required and accessibility handling -->
149
- <select v-model="selected" :required="$attrs['required']" style="display: none">
154
+ <select ref="nativeSelect" :required="$attrs['required']" multiple style="display: none">
150
155
  <option value=""></option>
151
156
  <option v-for="item in options" :value="optionKey ? item[optionKey] : item">{{ optionKey }} - {{ item[optionLabel] }}</option>
152
157
  </select>
153
158
 
154
- <Menu ref="menu" :model="menuModel" :close-on-activation="false" @close="onMenuClosed" :search-threshold="searchThreshold"></Menu>
159
+ <Menu ref="menu" :model="menuModel" :close-on-activation="false" @close="onClose" :search-threshold="searchThreshold"></Menu>
155
160
  {{ buttonLabel }}
156
161
  <Icon icon="fa-solid fa-chevron-down" class="pankow-button-icon-right-with-text" />
157
162
  </div>
@@ -6,6 +6,7 @@ const svgEye = '<svg aria-hidden="true" focusable="false" data-prefix="fas" data
6
6
  const svgEyeSlash = '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="eye-slash" class="svg-inline--fa fa-eye-slash fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M320 400c-75.85 0-137.25-58.71-142.9-133.11L72.2 185.82c-13.79 17.3-26.48 35.59-36.72 55.59a32.35 32.35 0 0 0 0 29.19C89.71 376.41 197.07 448 320 448c26.91 0 52.87-4 77.89-10.46L346 397.39a144.13 144.13 0 0 1-26 2.61zm313.82 58.1l-110.55-85.44a331.25 331.25 0 0 0 81.25-102.07 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64a308.15 308.15 0 0 0-147.32 37.7L45.46 3.37A16 16 0 0 0 23 6.18L3.37 31.45A16 16 0 0 0 6.18 53.9l588.36 454.73a16 16 0 0 0 22.46-2.81l19.64-25.27a16 16 0 0 0-2.82-22.45zm-183.72-142l-39.3-30.38A94.75 94.75 0 0 0 416 256a94.76 94.76 0 0 0-121.31-92.21A47.65 47.65 0 0 1 304 192a46.64 46.64 0 0 1-1.54 10l-73.61-56.89A142.31 142.31 0 0 1 320 112a143.92 143.92 0 0 1 144 144c0 21.63-5.29 41.79-13.9 60.11z"></path></svg>';
7
7
 
8
8
  const emit = defineEmits([ 'update:modelValue' ]);
9
+
9
10
  const props = defineProps({
10
11
  placeholder: String,
11
12
  modelValue: String,
@@ -67,11 +68,17 @@ function toggleReveal() {
67
68
  outline: none;
68
69
  }
69
70
 
70
- .pankow-password-input:disabled {
71
+ .pankow-password-input[readonly],
72
+ .pankow-password-input[disabled] {
71
73
  cursor: not-allowed;
72
- opacity: 0.5;
74
+ background-color: var(--pankow-input-readonly-background-color);
75
+ }
76
+
77
+ .pankow-password-input[disabled]:hover {
78
+ border-color: var(--pankow-input-border-color);
73
79
  }
74
80
 
81
+
75
82
  .pankow-password-reveal {
76
83
  display: inline-block;
77
84
  position: absolute;
@@ -32,7 +32,7 @@ const props = defineProps({
32
32
  },
33
33
  });
34
34
 
35
- const emits = defineEmits(['select']);
35
+ const emits = defineEmits(['select', 'close']);
36
36
 
37
37
  const model = defineModel();
38
38
  const selected = ref(null);
@@ -92,10 +92,6 @@ function selectIndex(index) {
92
92
  nativeSelect.value.dispatchEvent(new Event('change', { bubbles: true }));
93
93
  }
94
94
 
95
- function onMenuClosed() {
96
- elem.value.focus();
97
- }
98
-
99
95
  function onClick(event) {
100
96
  if (props.disabled) return;
101
97
 
@@ -109,8 +105,13 @@ function onOpen(event) {
109
105
  menu.value.open(event, elem.value);
110
106
  }
111
107
 
112
- function onClose(event) {
113
- menu.value.close();
108
+ function onClose(event = null) {
109
+ if (menu.value.isOpen) {
110
+ if (event) event.stopPropagation();
111
+ elem.value.focus();
112
+ menu.value.close();
113
+ emits('close');
114
+ }
114
115
  }
115
116
 
116
117
  function onClosed() {
@@ -169,14 +170,14 @@ watchEffect(handleDefaultSelect);
169
170
  </script>
170
171
 
171
172
  <template>
172
- <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.esc.stop="onClose" @keydown.stop="onKeyDown($event)">
173
+ <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.esc="onClose" @keydown.stop="onKeyDown($event)">
173
174
  <!-- native select for required and accessibility handling -->
174
175
  <select v-model="selectedKey" ref="nativeSelect" :required="$attrs['required']" style="display: none">
175
176
  <option value=""></option>
176
177
  <option v-for="item in options" :value="optionKey ? item[optionKey] : item">{{ item[optionLabel] }}</option>
177
178
  </select>
178
179
 
179
- <Menu ref="menu" :model="menuModel" :search-threshold="searchThreshold" :close-on-activation="true" @close="onMenuClosed"/>
180
+ <Menu ref="menu" :model="menuModel" :search-threshold="searchThreshold" :close-on-activation="true" @close="onClose"/>
180
181
  <span class="pankow-singleselect-label">
181
182
  <Icon v-if="selected ? selected.icon : false" :icon="selected.icon" style="margin-right: 6px" />
182
183
  {{ selected ? selected[optionLabel] : placeholder }}
@@ -1,5 +1,7 @@
1
1
  <script setup>
2
2
 
3
+ // Until we support input type as prop, make sure EmailInput.vue matches the features here
4
+
3
5
  import { useTemplateRef } from 'vue';
4
6
 
5
7
  const model = defineModel();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloudron/pankow",
3
3
  "private": false,
4
- "version": "3.5.9",
4
+ "version": "3.5.11",
5
5
  "description": "",
6
6
  "main": "index.js",
7
7
  "types": "types/index.d.ts",
@@ -19,12 +19,12 @@
19
19
  "@fontsource/inter": "^5.2.8",
20
20
  "@fortawesome/fontawesome-free": "^7.1.0",
21
21
  "filesize": "^11.0.13",
22
- "monaco-editor": "^0.54.0"
22
+ "monaco-editor": "^0.55.1"
23
23
  },
24
24
  "devDependencies": {
25
- "@vitejs/plugin-vue": "^6.0.1",
25
+ "@vitejs/plugin-vue": "^6.0.2",
26
26
  "typescript": "^5.9.3",
27
- "vite": "^7.2.2",
28
- "vue": "^3.5.24"
27
+ "vite": "^7.2.4",
28
+ "vue": "^3.5.25"
29
29
  }
30
30
  }
package/style.css CHANGED
@@ -183,6 +183,16 @@ textarea:focus {
183
183
  outline: none;
184
184
  }
185
185
 
186
+ textarea[disabled],
187
+ textarea[readonly] {
188
+ cursor: not-allowed;
189
+ background-color: var(--pankow-input-readonly-background-color);
190
+ }
191
+
192
+ textarea[disabled]:hover {
193
+ border-color: var(--pankow-input-border-color);
194
+ }
195
+
186
196
  @media (max-width: 576px) {
187
197
  .pankow-no-mobile {
188
198
  display: none !important;