@cloudron/pankow 3.2.16 → 3.2.17

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.
@@ -0,0 +1,319 @@
1
+ <script setup>
2
+
3
+ import { nextTick, useTemplateRef, computed, ref } from 'vue';
4
+
5
+ import MenuItem from './MenuItem.vue';
6
+ import MenuItemLink from './MenuItemLink.vue';
7
+ import TextInput from './TextInput.vue';
8
+
9
+ function getViewport() {
10
+ const win = window,
11
+ d = document,
12
+ e = d.documentElement,
13
+ g = d.getElementsByTagName('body')[0],
14
+ w = win.innerWidth || e.clientWidth || g.clientWidth,
15
+ h = win.innerHeight || e.clientHeight || g.clientHeight;
16
+
17
+ return {
18
+ width: w,
19
+ height: h
20
+ };
21
+ }
22
+
23
+ function getHiddenElementSize(element) {
24
+ if (element) {
25
+ const originalVisibility = element.style.visibility;
26
+ const originalDisplay = element.style.display;
27
+
28
+ element.style.visibility = 'hidden';
29
+ element.style.display = 'block';
30
+
31
+ element.clientHeight; // force reflow
32
+
33
+ const height = element.offsetHeight;
34
+ const width = element.offsetWidth;
35
+
36
+ element.style.display = originalDisplay;
37
+ element.style.visibility = originalVisibility;
38
+
39
+ return { height, width };
40
+ }
41
+ return { height: 0, width: 0 };
42
+ }
43
+
44
+
45
+ function getActiveElement(children) {
46
+ for (const child of children) {
47
+ if (child === document.activeElement) return child;
48
+ }
49
+ return null;
50
+ }
51
+
52
+ // outsideClickListener: null,
53
+ const container = useTemplateRef('container');
54
+ const itemElements = useTemplateRef('itemElements');
55
+
56
+ const emit = defineEmits([ 'close' ]);
57
+
58
+ const props = defineProps({
59
+ icon: String,
60
+ model: Array,
61
+ closeOnActivation: {
62
+ type: Boolean,
63
+ default: true,
64
+ },
65
+ searchThreshold: {
66
+ type: Number,
67
+ default: Infinity,
68
+ }
69
+ });
70
+
71
+ const openEventTimeStamp = ref(0);
72
+ const forElement = ref(null);
73
+ const isOpen = ref(false);
74
+ let pageX = 0;
75
+ let targetBottom = 0;
76
+ let targetTop = 0;
77
+ const offsetY = ref(0);
78
+ const rollUp = ref(false);
79
+ const searchString = ref('');
80
+ const emptyItem = ref({
81
+ label: '',
82
+ disabled: true,
83
+ });
84
+
85
+ const hasIcons = computed(() => {
86
+ return !!props.model.find((item) => !!item.icon);
87
+ });
88
+
89
+ const visibleItems = computed(() => {
90
+ if (!searchString.value) return props.model;
91
+ const s = searchString.value.toLowerCase();
92
+ return props.model.filter((item) => item.label.toLowerCase().indexOf(s) !== -1);
93
+ });
94
+
95
+ // select menu item by .label
96
+ function select(elem) {
97
+ let next = container.value.children[0];
98
+ while (true) {
99
+ if (!next) return;
100
+
101
+ if (next.innerText !== elem.label) {
102
+ next = next.nextElementSibling;
103
+ continue;
104
+ }
105
+
106
+ next.focus();
107
+ break;
108
+ }
109
+ }
110
+
111
+ let search = '';
112
+ let debounceTimer = null;
113
+ function onKeyDown(event) {
114
+ // only handle alphanumerics
115
+ if (!/^[a-zA-Z0-9]$/.test(event.key)) return;
116
+
117
+ const key = event.key.toLowerCase();
118
+
119
+ if (debounceTimer) clearTimeout(debounceTimer);
120
+ debounceTimer = setTimeout(() => search = '', 500);
121
+
122
+ search += key;
123
+
124
+ const found = visibleItems.value.find(elem => {
125
+ if (!elem.label) return false;
126
+ return elem.label.toLowerCase().indexOf(search) === 0;
127
+ });
128
+
129
+ if (found) select(found)
130
+ }
131
+
132
+ function onItemActivated(item) {
133
+ if (item.action instanceof Function) item.action(item);
134
+ if (props.closeOnActivation) close();
135
+ }
136
+
137
+ async function open(event, element = null) {
138
+ isOpen.value = true;
139
+ pageX = element ? element.getBoundingClientRect().left : event.pageX;
140
+ targetBottom = element ? element.getBoundingClientRect().bottom : event.pageY;
141
+ targetTop = element ? element.getBoundingClientRect().top : event.pageY;
142
+ offsetY.value = element ? (element.getBoundingClientRect().height + 2) : 0; // offset in case we roll up
143
+ forElement.value = element;
144
+
145
+ if (!container.value) return;
146
+
147
+ if (element) container.value.style.minWidth = element.getBoundingClientRect().width + 'px';
148
+
149
+ await nextTick();
150
+
151
+ position();
152
+
153
+ openEventTimeStamp.value = event.timeStamp;
154
+
155
+ event.preventDefault();
156
+
157
+ // if opened from a key action, immediately select first
158
+ if (event.type === 'keydown' && event.key === 'ArrowUp') selectUp();
159
+ else if (event.type === 'keydown') selectDown();
160
+ else container.value.focus();
161
+ }
162
+
163
+ function selectUp() {
164
+ if (!container.value.children[0]) return;
165
+
166
+ const active = getActiveElement(container.value.children);
167
+
168
+ if (!active) return container.value.lastElementChild.focus();
169
+
170
+ let prev = active;
171
+ while (true) {
172
+ prev = prev.previousElementSibling;
173
+ if (!prev) return;
174
+
175
+ if (prev.getAttribute('separator') === 'true') continue;
176
+ else if (prev.classList.contains('pankow-menu-item-disabled')) continue;
177
+
178
+ prev.focus();
179
+ break;
180
+ }
181
+ }
182
+
183
+ function selectDown() {
184
+ if (!container.value.children[0]) return;
185
+
186
+ const active = getActiveElement(container.value.children);
187
+
188
+ if (!active) return container.value.firstElementChild.focus();
189
+
190
+ let next = active;
191
+ while (true) {
192
+ next = next.nextElementSibling;
193
+ if (!next) return;
194
+
195
+ if (next.getAttribute('separator') === 'true') continue;
196
+ else if (next.classList.contains('pankow-menu-item-disabled')) continue;
197
+
198
+ next.focus();
199
+ break;
200
+ }
201
+ }
202
+
203
+ function onBackdrop(event) {
204
+ close();
205
+ event.preventDefault();
206
+ }
207
+
208
+ function close() {
209
+ isOpen.value = false;
210
+ container.value.style.maxHeight = 'unset';
211
+ container.value.style.bottom = 'unset';
212
+ container.value.style.top = 'unset';
213
+ emit('close');
214
+ }
215
+
216
+ function position() {
217
+ if (!container.value) return;
218
+
219
+ const size = getHiddenElementSize(container.value);
220
+
221
+ let left = pageX;
222
+ let top = targetBottom + 1;
223
+ let width = container.value.offsetParent ? container.value.offsetWidth : size.width;
224
+ let height = container.value.offsetParent ? container.value.offsetHeight : size.height;
225
+ let viewport = getViewport();
226
+
227
+ let bottom = viewport.height - targetTop + 1;
228
+
229
+ //flip
230
+ if (left + width - document.body.scrollLeft > viewport.width) {
231
+ // if this is like a dropdown right align instead of flip
232
+ if (forElement.value) {
233
+ left = forElement.value.getBoundingClientRect().left + (forElement.value.getBoundingClientRect().width - width);
234
+ } else {
235
+ left -= width;
236
+ }
237
+ }
238
+
239
+ //flip
240
+ if (top + height - document.body.scrollTop > viewport.height) {
241
+ if (top - document.body.scrollTop > viewport.height/2) {
242
+ rollUp.value = true;
243
+ } else {
244
+ rollUp.value = false;
245
+ }
246
+ } else {
247
+ rollUp.value = false;
248
+ }
249
+
250
+ //fit
251
+ if (left < document.body.scrollLeft) {
252
+ left = document.body.scrollLeft;
253
+ }
254
+
255
+ container.value.style.left = left + 'px';
256
+ if (rollUp.value) {
257
+ container.value.style.top = 'unset';
258
+ container.value.style.bottom = bottom + 'px';
259
+ container.value.style.maxHeight = viewport.height - bottom + 'px';
260
+ container.value.style.height = 'auto';
261
+ } else {
262
+ container.value.style.top = top + 'px';
263
+ container.value.style.bottom = 'unset';
264
+ container.value.style.maxHeight = viewport.height - top + 'px';
265
+ }
266
+ }
267
+
268
+ defineExpose({
269
+ open,
270
+ close,
271
+ });
272
+
273
+ </script>
274
+
275
+ <template>
276
+ <teleport to="#app">
277
+ <div class="pankow-menu-backdrop" @click="onBackdrop($event)" @contextmenu="onBackdrop($event)" v-show="isOpen"></div>
278
+ <Transition :name="rollUp ? 'pankow-roll-up' : 'pankow-roll-down'">
279
+ <div class="pankow-menu" v-show="isOpen" ref="container" tabindex="0" @keydown.up.stop="selectUp()" @keydown.down.stop="selectDown()" @keydown.esc.stop="close()" @keydown="onKeyDown">
280
+ <TextInput placeholder="Filter ..." style="border: 0; padding: 8px 12px;" v-model="searchString" v-if="searchThreshold < model.length"/>
281
+ <component v-for="item in visibleItems" ref="itemElements" :is="item.type || (item.href ? MenuItemLink : MenuItem)" @activated="onItemActivated(item)" :item="item" :has-icons="hasIcons" />
282
+ <MenuItem v-if="model.length === 0" :item="emptyItem"/>
283
+ </div>
284
+ </Transition>
285
+ </teleport>
286
+ </template>
287
+
288
+ <style>
289
+
290
+ .pankow-menu-backdrop {
291
+ position: fixed;
292
+ height: 100%;
293
+ width: 100%;
294
+ left: 0px;
295
+ top: 0px;
296
+ z-index: 3001;
297
+ background-color: transparent;
298
+ }
299
+
300
+ .pankow-menu {
301
+ position: fixed;
302
+ box-shadow: var(--pankow-menu-shadow);
303
+ border-radius: var(--pankow-border-radius);
304
+ background-color: var(--pankow-input-background-color);
305
+ min-width: 120px;
306
+ z-index: 3001;
307
+ color: var(--pankow-color-dark);
308
+ overflow: auto;
309
+ /*max-height: 100%;*/
310
+ outline: none;
311
+ }
312
+
313
+ @media (prefers-color-scheme: dark) {
314
+ .pankow-menu {
315
+ color: var(--pankow-color-light-dark);
316
+ }
317
+ }
318
+
319
+ </style>
@@ -170,7 +170,7 @@ onMounted(() => {
170
170
  border-style: solid;
171
171
  border-color: var(--pankow-input-border-color);
172
172
  background-color: var(--pankow-input-background-color);
173
- padding: 5px 12px;
173
+ padding: var(--pankow-input-vertial-padding) max(12px, var(--pankow-input-horizontal-padding));
174
174
  border-radius: var(--pankow-border-radius);
175
175
  text-decoration: none;
176
176
  cursor: pointer;
@@ -179,7 +179,7 @@ onMounted(() => {
179
179
  border-width: 1px;
180
180
  border-style: solid;
181
181
  border-color: var(--pankow-input-border-color);
182
- padding: 5px 12px;
182
+ padding: var(--pankow-input-vertial-padding) max(12px, var(--pankow-input-horizontal-padding));
183
183
  border-radius: var(--pankow-border-radius);
184
184
  background-color: var(--pankow-input-background-color);
185
185
  text-decoration: none;
@@ -1,5 +1,7 @@
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,
@@ -8,11 +10,18 @@ const props = defineProps({
8
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="text" :placeholder="placeholder" :readonly="readonly" v-model.trim="model"/>
24
+ <input ref="inputElement" class="pankow-text-input" type="text" :placeholder="placeholder" :readonly="readonly" v-model.trim="model"/>
16
25
  </template>
17
26
 
18
27
  <style>
@@ -48,6 +57,7 @@ const props = defineProps({
48
57
  .pankow-text-input[readonly] {
49
58
  cursor: not-allowed;
50
59
  border-color: var(--pankow-input-border-color) !important;
60
+ background-color: #f5f5f5;
51
61
  }
52
62
 
53
63
  .pankow-text-input:disabled {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloudron/pankow",
3
3
  "private": false,
4
- "version": "3.2.16",
4
+ "version": "3.2.17",
5
5
  "description": "",
6
6
  "main": "index.js",
7
7
  "types": "types/index.d.ts",
@@ -16,15 +16,15 @@
16
16
  "author": "",
17
17
  "license": "ISC",
18
18
  "dependencies": {
19
- "@fontsource/inter": "^5.2.6",
20
- "@fortawesome/fontawesome-free": "^7.0.0",
19
+ "@fontsource/inter": "^5.2.7",
20
+ "@fortawesome/fontawesome-free": "^7.0.1",
21
21
  "filesize": "^11.0.2",
22
- "monaco-editor": "^0.52.2"
22
+ "monaco-editor": "^0.53.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@vitejs/plugin-vue": "^6.0.1",
26
26
  "typescript": "^5.9.2",
27
- "vite": "^7.1.3",
28
- "vue": "^3.5.20"
27
+ "vite": "^7.1.5",
28
+ "vue": "^3.5.21"
29
29
  }
30
30
  }