@cloudron/pankow 4.1.15 → 4.2.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.
@@ -18,8 +18,8 @@
18
18
  border-radius: 0;
19
19
  margin-left: 0;
20
20
  margin-right: 0;
21
- border-left-width: 0.5px;
22
- border-right-width: 0.5px;
21
+ border-left-width: 1px;
22
+ border-right-width: 0;
23
23
  }
24
24
 
25
25
  .pankow-button-group > .pankow-button:first-child {
@@ -62,13 +62,16 @@ const mergedStyle = computed(() => {
62
62
  const visible = ref(false);
63
63
  const dialog = useTemplateRef('dialog');
64
64
  const dialogBody = useTemplateRef('dialogBody');
65
+ const confirmButton = useTemplateRef('confirmButton');
65
66
 
66
67
  async function open() {
67
68
  visible.value = true;
68
69
 
69
- // try to focus some form elements else just the dialog
70
+ // try to focus some form elements, else confirm button, else just the dialog
70
71
  await nextTick();
71
- const focusElement = dialogBody.value.querySelector('input:not([disabled]):not([readonly]):not([style*="display:none"]):not([style*="display: none"])') || dialog.value;
72
+ const focusElement = dialogBody.value.querySelector('input:not([disabled]):not([readonly]):not([style*="display:none"]):not([style*="display: none"])')
73
+ || confirmButton.value?.$el
74
+ || dialog.value;
72
75
  focusElement.focus();
73
76
  }
74
77
 
@@ -125,7 +128,7 @@ defineExpose({ open, close });
125
128
  <Button :[alternateStyle]="!!alternateStyle" @click="onAlternateAction" v-show="alternateLabel" :loading="alternateBusy" :disabled="alternateBusy || !alternateActive">{{ alternateLabel }}</Button>
126
129
  <div style="flex-grow: 1;"></div>
127
130
  <Button :[rejectStyle]="!!rejectStyle" @click="onReject" v-show="rejectLabel" :disabled="!rejectActive">{{ rejectLabel }}</Button>
128
- <Button :[confirmStyle]="!!confirmStyle" @click="onConfirm" v-show="confirmLabel" :loading="confirmBusy" :disabled="confirmBusy || !confirmActive">{{ confirmLabel }}</Button>
131
+ <Button ref="confirmButton" :[confirmStyle]="!!confirmStyle" @click="onConfirm" v-show="confirmLabel" :loading="confirmBusy" :disabled="confirmBusy || !confirmActive">{{ confirmLabel }}</Button>
129
132
  </div>
130
133
  <Icon v-if="showX" class="pankow-dialog-x-close" icon="fa-solid fa-xmark" style="cursor: pointer;" @click="onReject"/>
131
134
  </div>
@@ -12,6 +12,7 @@
12
12
  .pankow-input-group {
13
13
  display: flex;
14
14
  flex-direction: row;
15
+ min-width: 0;
15
16
  }
16
17
 
17
18
  .pankow-input-group > * {
@@ -31,6 +31,7 @@ const props = defineProps({
31
31
  .pankow-main-layout-outer {
32
32
  width: 100%;
33
33
  height: 100%;
34
+ min-width: 0;
34
35
  }
35
36
 
36
37
  .pankow-main-layout-container {
@@ -39,6 +40,7 @@ const props = defineProps({
39
40
  display: flex;
40
41
  flex-direction: column;
41
42
  flex-wrap: nowrap;
43
+ min-width: 0;
42
44
  }
43
45
 
44
46
  .pankow-main-layout-header {
@@ -48,8 +50,10 @@ const props = defineProps({
48
50
  .pankow-main-layout-body {
49
51
  width: 100%;
50
52
  flex-grow: 1;
51
- overflow: auto;
53
+ overflow-x: hidden;
54
+ overflow-y: auto;
52
55
  min-height: 2em;
56
+ min-width: 0;
53
57
  }
54
58
 
55
59
  .pankow-main-layout-footer {
@@ -37,6 +37,10 @@ const props = defineProps({
37
37
  type: Number,
38
38
  default: Infinity,
39
39
  },
40
+ showDropdown: {
41
+ type: Boolean,
42
+ default: true
43
+ },
40
44
  });
41
45
 
42
46
  const emits = defineEmits(['select', 'close']);
@@ -167,7 +171,7 @@ onMounted(() => {
167
171
 
168
172
  <Menu ref="menu" :model="menuModel" :close-on-activation="false" @close="onClose" :search-threshold="searchThreshold"></Menu>
169
173
  {{ buttonLabel }}
170
- <Icon icon="fa-solid fa-chevron-down" class="pankow-button-icon-right-with-text" />
174
+ <Icon v-show="showDropdown" icon="fa-solid fa-chevron-down" class="pankow-button-icon-right-with-text" />
171
175
  </div>
172
176
  </template>
173
177
 
@@ -31,6 +31,10 @@ const props = defineProps({
31
31
  type: Number,
32
32
  default: Infinity,
33
33
  },
34
+ showDropdown: {
35
+ type: Boolean,
36
+ default: true
37
+ },
34
38
  });
35
39
 
36
40
  const emits = defineEmits(['select', 'close']);
@@ -181,10 +185,10 @@ watchEffect(handleDefaultSelect);
181
185
 
182
186
  <Menu ref="menu" :model="menuModel" :search-threshold="searchThreshold" :close-on-activation="true" @close="onClose"/>
183
187
  <span class="pankow-singleselect-label">
184
- <Icon v-if="selected ? selected.icon : false" :icon="selected.icon" style="margin-right: 6px" />
185
- {{ selected ? selected[optionLabel] : placeholder }}
188
+ <Icon v-if="selected ? selected.icon : false" :icon="selected.icon" class="pankow-singleselect-label-icon" />
189
+ <span class="pankow-singleselect-label-text">{{ selected ? selected[optionLabel] : placeholder }}</span>
186
190
  </span>
187
- <Icon icon="fa-solid fa-chevron-down" class="pankow-button-icon-right-with-text" />
191
+ <Icon v-show="showDropdown" icon="fa-solid fa-chevron-down" class="pankow-button-icon-right-with-text" />
188
192
  </div>
189
193
  </template>
190
194
 
@@ -208,8 +212,8 @@ watchEffect(handleDefaultSelect);
208
212
  text-decoration: none;
209
213
  cursor: pointer;
210
214
  transition: background-color 250ms;
211
- min-width: 100px;
212
- white-space: nowrap;
215
+ min-width: 0;
216
+ max-width: 100%;
213
217
  overflow: hidden;
214
218
  }
215
219
 
@@ -235,8 +239,29 @@ watchEffect(handleDefaultSelect);
235
239
  }
236
240
 
237
241
  .pankow-singleselect-label {
242
+ display: flex;
243
+ align-items: center;
244
+ min-width: 0;
245
+ flex: 1 1 auto;
246
+ overflow: hidden;
247
+ text-align: start;
248
+ }
249
+
250
+ .pankow-singleselect-label-icon {
251
+ margin-right: 6px;
252
+ flex-shrink: 0;
253
+ }
254
+
255
+ .pankow-singleselect-label-text {
256
+ min-width: 0;
257
+ flex: 1 1 auto;
238
258
  overflow: hidden;
239
259
  text-overflow: ellipsis;
260
+ white-space: nowrap;
261
+ }
262
+
263
+ .pankow-singleselect > .pankow-button-icon-right-with-text {
264
+ flex-shrink: 0;
240
265
  }
241
266
 
242
267
  </style>
@@ -0,0 +1,162 @@
1
+ <script setup>
2
+ import { ref, computed, watch, onMounted, onUnmounted, useTemplateRef } from 'vue';
3
+
4
+ const props = defineProps({
5
+ leftWidth: {
6
+ type: Number,
7
+ default: 50
8
+ },
9
+ minLeftWidth: {
10
+ type: Number,
11
+ default: 20
12
+ },
13
+ minRightWidth: {
14
+ type: Number,
15
+ default: 20
16
+ }
17
+ });
18
+
19
+ const emit = defineEmits(['update:leftWidth']);
20
+
21
+ const containerRef = useTemplateRef('container');
22
+ const isDragging = ref(false);
23
+ const dragStartX = ref(0);
24
+ const dragStartWidth = ref(0);
25
+ const currentLeftWidth = ref(props.leftWidth);
26
+
27
+ watch(() => props.leftWidth, (val) => {
28
+ if (!isDragging.value) {
29
+ currentLeftWidth.value = val;
30
+ }
31
+ });
32
+
33
+ function startDrag(e) {
34
+ isDragging.value = true;
35
+ dragStartX.value = e.clientX ?? e.touches?.[0]?.clientX ?? 0;
36
+ dragStartWidth.value = currentLeftWidth.value;
37
+ document.body.style.cursor = 'col-resize';
38
+ document.body.style.userSelect = 'none';
39
+ e.preventDefault();
40
+ }
41
+
42
+ function onDrag(e) {
43
+ if (!isDragging.value || !containerRef.value) return;
44
+ const clientX = e.clientX ?? e.touches?.[0]?.clientX ?? 0;
45
+ const containerWidth = containerRef.value.getBoundingClientRect().width;
46
+ if (containerWidth === 0) return;
47
+ const delta = ((clientX - dragStartX.value) / containerWidth) * 100;
48
+ const newWidth = dragStartWidth.value + delta;
49
+ currentLeftWidth.value = Math.max(props.minLeftWidth, Math.min(100 - props.minRightWidth, newWidth));
50
+ }
51
+
52
+ function stopDrag() {
53
+ if (!isDragging.value) return;
54
+ isDragging.value = false;
55
+ document.body.style.cursor = '';
56
+ document.body.style.userSelect = '';
57
+ emit('update:leftWidth', Math.round(currentLeftWidth.value));
58
+ }
59
+
60
+ onMounted(() => {
61
+ document.addEventListener('mousemove', onDrag);
62
+ document.addEventListener('mouseup', stopDrag);
63
+ document.addEventListener('touchmove', onDrag, { passive: false });
64
+ document.addEventListener('touchend', stopDrag);
65
+ });
66
+
67
+ onUnmounted(() => {
68
+ document.removeEventListener('mousemove', onDrag);
69
+ document.removeEventListener('mouseup', stopDrag);
70
+ document.removeEventListener('touchmove', onDrag);
71
+ document.removeEventListener('touchend', stopDrag);
72
+ });
73
+
74
+ const leftStyle = computed(() => ({
75
+ flex: `0 0 ${currentLeftWidth.value}%`,
76
+ maxWidth: `${currentLeftWidth.value}%`
77
+ }));
78
+ </script>
79
+
80
+ <template>
81
+ <div ref="container" class="pankow-split-layout" :class="{ 'pankow-split-layout-dragging': isDragging }">
82
+ <div class="pankow-split-layout-left" :style="leftStyle">
83
+ <slot name="left" />
84
+ </div>
85
+ <div class="pankow-split-layout-divider" @mousedown="startDrag" @touchstart.prevent="startDrag">
86
+ <div class="pankow-split-layout-handle"><span></span><span></span></div>
87
+ </div>
88
+ <div class="pankow-split-layout-right">
89
+ <slot name="right" />
90
+ </div>
91
+ </div>
92
+ </template>
93
+
94
+ <style>
95
+ .pankow-split-layout {
96
+ display: flex;
97
+ height: 100%;
98
+ width: 100%;
99
+ min-width: 0;
100
+ overflow: hidden;
101
+ }
102
+
103
+ .pankow-split-layout-left {
104
+ height: 100%;
105
+ min-width: 0;
106
+ overflow: hidden;
107
+ }
108
+
109
+ .pankow-split-layout-divider {
110
+ width: 6px;
111
+ cursor: col-resize;
112
+ background: transparent;
113
+ position: relative;
114
+ flex-shrink: 0;
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ }
119
+
120
+ .pankow-split-layout-divider:hover,
121
+ .pankow-split-layout-dragging .pankow-split-layout-divider {
122
+ background: var(--pankow-input-border-color);
123
+ }
124
+
125
+ .pankow-split-layout-handle {
126
+ position: absolute;
127
+ top: 50%;
128
+ left: 50%;
129
+ transform: translate(-50%, -50%);
130
+ display: flex;
131
+ flex-direction: column;
132
+ gap: 2px;
133
+ opacity: 0;
134
+ transition: opacity 0.15s;
135
+ pointer-events: none;
136
+ }
137
+
138
+ .pankow-split-layout-divider:hover .pankow-split-layout-handle,
139
+ .pankow-split-layout-dragging .pankow-split-layout-handle {
140
+ opacity: 0.5;
141
+ }
142
+
143
+ .pankow-split-layout-handle span {
144
+ display: block;
145
+ width: 2px;
146
+ height: 16px;
147
+ border-radius: 1px;
148
+ background: var(--pankow-color-text);
149
+ }
150
+
151
+ .pankow-split-layout-right {
152
+ flex: 1;
153
+ height: 100%;
154
+ min-width: 0;
155
+ overflow: hidden;
156
+ }
157
+
158
+ .pankow-split-layout-dragging .pankow-split-layout-left,
159
+ .pankow-split-layout-dragging .pankow-split-layout-right {
160
+ pointer-events: none;
161
+ }
162
+ </style>
@@ -174,7 +174,6 @@ onMounted(() => {
174
174
  font-size: 14px;
175
175
  padding: 5px;
176
176
  user-select: none;
177
- border-bottom: 1px solid var(--pankow-color-light-dark);
178
177
  white-space: nowrap;
179
178
  font-weight: var(--pankow-font-weight-bold);
180
179
  }
@@ -45,24 +45,28 @@ const props = defineProps({
45
45
  display: flex;
46
46
  justify-content: space-between;
47
47
  padding: 5px 10px;
48
+ min-width: 0;
48
49
  }
49
50
 
50
51
  .pankow-top-bar-left {
51
52
  display: flex;
52
53
  margin: auto 0px;
53
54
  align-items: center;
55
+ min-width: 0;
54
56
  }
55
57
 
56
58
  .pankow-top-bar-center {
57
59
  display: flex;
58
60
  margin: auto 0px;
59
61
  align-items: center;
62
+ min-width: 0;
60
63
  }
61
64
 
62
65
  .pankow-top-bar-right {
63
66
  display: flex;
64
67
  margin: auto 0px;
65
68
  align-items: center;
69
+ min-width: 0;
66
70
  }
67
71
 
68
72
  .pankow-top-bar-grow {
package/fetcher.js CHANGED
@@ -5,6 +5,18 @@ const globalOptions = {
5
5
  errorHook: null,
6
6
  };
7
7
 
8
+ /**
9
+ * Low-level HTTP helper. `options` is merged into the native `fetch()` init object
10
+ * (after {@link globalOptions}), so you may pass e.g. `{ signal: abortController.signal }`
11
+ * to cancel the request.
12
+ *
13
+ * @param {string} uri
14
+ * @param {string} method
15
+ * @param {Object} headers
16
+ * @param {Object} query Plain key/value map serialized as the URL query string
17
+ * @param {Object|FormData|null} body
18
+ * @param {RequestInit} [options] Extra fetch options; `signal` is supported for cancellation
19
+ */
8
20
  async function request(uri, method, headers, query, body, options) {
9
21
  let response, responseBody;
10
22
 
@@ -52,22 +64,27 @@ async function request(uri, method, headers, query, body, options) {
52
64
  return { status: response.status, body: responseBody };
53
65
  }
54
66
 
67
+ /** @param {RequestInit} [options] Merged into `fetch()`; use `{ signal }` to abort. */
55
68
  async function head(uri, query = {}, options = {}) {
56
69
  return await request(uri, 'HEAD', {}, query, null, options);
57
70
  }
58
71
 
72
+ /** @param {RequestInit} [options] Merged into `fetch()`; use `{ signal }` to abort. */
59
73
  async function get(uri, query = {}, options = {}) {
60
74
  return await request(uri, 'GET', {}, query, null, options);
61
75
  }
62
76
 
77
+ /** @param {RequestInit} [options] Merged into `fetch()`; use `{ signal }` to abort. */
63
78
  async function post(uri, body, query = {}, options = {}) {
64
79
  return await request(uri, 'POST', {}, query, body, options);
65
80
  }
66
81
 
82
+ /** @param {RequestInit} [options] Merged into `fetch()`; use `{ signal }` to abort. */
67
83
  async function put(uri, body, query = {}, options = {}) {
68
84
  return await request(uri, 'PUT', {}, query, body, options);
69
85
  }
70
86
 
87
+ /** @param {RequestInit} [options] Merged into `fetch()`; use `{ signal }` to abort. */
71
88
  async function del(uri, body, query = {}, options = {}) {
72
89
  return await request(uri, 'DELETE', {}, query, body, options);
73
90
  }
package/index.js CHANGED
@@ -36,6 +36,7 @@ import Popover from './components/Popover.vue';
36
36
  import ProgressBar from './components/ProgressBar.vue';
37
37
  import Radiobutton from './components/Radiobutton.vue';
38
38
  import SideBar from './components/SideBar.vue';
39
+ import SplitLayout from './components/SplitLayout.vue';
39
40
  import Spinner from './components/Spinner.vue';
40
41
  import Switch from './components/Switch.vue';
41
42
  import TableView from './components/TableView.vue';
@@ -85,6 +86,7 @@ export {
85
86
  ProgressBar,
86
87
  Radiobutton,
87
88
  SideBar,
89
+ SplitLayout,
88
90
  Spinner,
89
91
  Switch,
90
92
  TableView,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloudron/pankow",
3
3
  "private": false,
4
- "version": "4.1.15",
4
+ "version": "4.2.1",
5
5
  "description": "",
6
6
  "main": "index.js",
7
7
  "types": "types/index.d.ts",
@@ -30,11 +30,11 @@
30
30
  },
31
31
  "devDependencies": {
32
32
  "@highlightjs/vue-plugin": "^2.1.0",
33
- "@vitejs/plugin-vue": "^6.0.6",
33
+ "@vitejs/plugin-vue": "^6.0.7",
34
34
  "highlight.js": "^11.11.1",
35
35
  "typescript": "^6.0.3",
36
- "vite": "^8.0.10",
37
- "vue": "^3.5.33",
38
- "vue-router": "^5.0.6"
36
+ "vite": "^8.0.16",
37
+ "vue": "^3.5.38",
38
+ "vue-router": "^5.1.0"
39
39
  }
40
40
  }
@@ -13,23 +13,28 @@ declare namespace globalOptions {
13
13
  let redirect: string;
14
14
  let errorHook: null;
15
15
  }
16
- declare function head(uri: any, query?: {}, options?: {}): Promise<{
16
+ /** @param {RequestInit} [options] Merged into `fetch()`; use `{ signal }` to abort. */
17
+ declare function head(uri: any, query?: {}, options?: RequestInit): Promise<{
17
18
  status: number;
18
19
  body: any;
19
20
  }>;
20
- declare function get(uri: any, query?: {}, options?: {}): Promise<{
21
+ /** @param {RequestInit} [options] Merged into `fetch()`; use `{ signal }` to abort. */
22
+ declare function get(uri: any, query?: {}, options?: RequestInit): Promise<{
21
23
  status: number;
22
24
  body: any;
23
25
  }>;
24
- declare function post(uri: any, body: any, query?: {}, options?: {}): Promise<{
26
+ /** @param {RequestInit} [options] Merged into `fetch()`; use `{ signal }` to abort. */
27
+ declare function post(uri: any, body: any, query?: {}, options?: RequestInit): Promise<{
25
28
  status: number;
26
29
  body: any;
27
30
  }>;
28
- declare function put(uri: any, body: any, query?: {}, options?: {}): Promise<{
31
+ /** @param {RequestInit} [options] Merged into `fetch()`; use `{ signal }` to abort. */
32
+ declare function put(uri: any, body: any, query?: {}, options?: RequestInit): Promise<{
29
33
  status: number;
30
34
  body: any;
31
35
  }>;
32
- declare function del(uri: any, body: any, query?: {}, options?: {}): Promise<{
36
+ /** @param {RequestInit} [options] Merged into `fetch()`; use `{ signal }` to abort. */
37
+ declare function del(uri: any, body: any, query?: {}, options?: RequestInit): Promise<{
33
38
  status: number;
34
39
  body: any;
35
40
  }>;
package/types/index.d.ts CHANGED
@@ -3,4 +3,4 @@ import gestures from './gestures.js';
3
3
  import tooltip from './tooltip.js';
4
4
  import fallbackImage from './fallbackImage.js';
5
5
  import utils from './utils.js';
6
- export { BottomBar, Breadcrumb, Button, ButtonGroup, ClipboardAction, ClipboardButton, Checkbox, DateTimeInput, Dialog, DirectoryView, EmailInput, FileUploader, FormGroup, InputGroup, Icon, InputDialog, LoginView, MainLayout, MaskedInput, Menu, MenuItem, SingleSelect, MultiSelect, Notification, NumberInput, OfflineBanner, PasswordInput, Popover, ProgressBar, Radiobutton, SideBar, Spinner, Switch, TableView, TabView, TagInput, TextInput, TextInputRaw, TopBar, TreeView, fetcher, gestures, tooltip, fallbackImage, utils };
6
+ export { BottomBar, Breadcrumb, Button, ButtonGroup, ClipboardAction, ClipboardButton, Checkbox, DateTimeInput, Dialog, DirectoryView, EmailInput, FileUploader, FormGroup, InputGroup, Icon, InputDialog, LoginView, MainLayout, MaskedInput, Menu, MenuItem, SingleSelect, MultiSelect, Notification, NumberInput, OfflineBanner, PasswordInput, Popover, ProgressBar, Radiobutton, SideBar, SplitLayout, Spinner, Switch, TableView, TabView, TagInput, TextInput, TextInputRaw, TopBar, TreeView, fetcher, gestures, tooltip, fallbackImage, utils };