@thinkpixellab-public/px-vue 4.1.13 → 4.1.14
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/PxArrowScroller.vue +1 -1
- package/components/PxFloat.vue +10 -0
- package/components/PxToggle.vue +47 -4
- package/components/PxToggleButton.vue +6 -2
- package/components/PxToggleButtonList.vue +19 -1
- package/components/PxToolPanel.vue +606 -0
- package/composables/useSettings.js +24 -5
- package/package.json +1 -1
- package/stories/PxToolPanel.stories.js +351 -0
- package/utils/balanceText.js +33 -1
- package/utils/utils.js +63 -7
- package/utils/utils.test.js +52 -0
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<teleport to="body" :disabled="!teleportToBody">
|
|
3
|
+
<div
|
|
4
|
+
v-show="ready"
|
|
5
|
+
:class="[bem({ teleport: teleportToBody }), panelClass]"
|
|
6
|
+
ref="panel"
|
|
7
|
+
:style="cssPosition"
|
|
8
|
+
>
|
|
9
|
+
<div
|
|
10
|
+
v-if="shouldShowHeader"
|
|
11
|
+
:class="[bem('header'), headerClass]"
|
|
12
|
+
:data-tool-panel-drag="allowDrag"
|
|
13
|
+
>
|
|
14
|
+
<slot name="header" :title="title">
|
|
15
|
+
<span>{{ title }}</span>
|
|
16
|
+
</slot>
|
|
17
|
+
</div>
|
|
18
|
+
<div :class="bem('content')">
|
|
19
|
+
<slot></slot>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div
|
|
23
|
+
v-show="canResizeNorth"
|
|
24
|
+
:class="bem('resize-handle', { n: true })"
|
|
25
|
+
data-resize-handle="n"
|
|
26
|
+
></div>
|
|
27
|
+
<div
|
|
28
|
+
v-show="canResizeSouth"
|
|
29
|
+
:class="bem('resize-handle', { s: true })"
|
|
30
|
+
data-resize-handle="s"
|
|
31
|
+
></div>
|
|
32
|
+
<div
|
|
33
|
+
v-show="canResizeEast"
|
|
34
|
+
:class="bem('resize-handle', { e: true })"
|
|
35
|
+
data-resize-handle="e"
|
|
36
|
+
></div>
|
|
37
|
+
<div
|
|
38
|
+
v-show="canResizeWest"
|
|
39
|
+
:class="bem('resize-handle', { w: true })"
|
|
40
|
+
data-resize-handle="w"
|
|
41
|
+
></div>
|
|
42
|
+
<!-- Corner handles for diagonal resizing -->
|
|
43
|
+
<div
|
|
44
|
+
v-show="canResizeNorth && canResizeEast"
|
|
45
|
+
:class="bem('resize-handle', { ne: true })"
|
|
46
|
+
data-resize-handle="ne"
|
|
47
|
+
></div>
|
|
48
|
+
<div
|
|
49
|
+
v-show="canResizeNorth && canResizeWest"
|
|
50
|
+
:class="bem('resize-handle', { nw: true })"
|
|
51
|
+
data-resize-handle="nw"
|
|
52
|
+
></div>
|
|
53
|
+
<div
|
|
54
|
+
v-show="canResizeSouth && canResizeEast"
|
|
55
|
+
:class="bem('resize-handle', { se: true })"
|
|
56
|
+
data-resize-handle="se"
|
|
57
|
+
></div>
|
|
58
|
+
<div
|
|
59
|
+
v-show="canResizeSouth && canResizeWest"
|
|
60
|
+
:class="bem('resize-handle', { sw: true })"
|
|
61
|
+
data-resize-handle="sw"
|
|
62
|
+
></div>
|
|
63
|
+
</div>
|
|
64
|
+
</teleport>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<script>
|
|
68
|
+
import Drag from '../utils/drag.js';
|
|
69
|
+
|
|
70
|
+
export default {
|
|
71
|
+
name: 'px-tool-panel',
|
|
72
|
+
props: {
|
|
73
|
+
/** Initial bounds object that defines the position and dimensions */
|
|
74
|
+
bounds: { type: Object, default: null },
|
|
75
|
+
|
|
76
|
+
/** Teleport component to body instead of positioning within parent element */
|
|
77
|
+
teleportToBody: { type: Boolean, default: false },
|
|
78
|
+
|
|
79
|
+
/** Show the header section of the panel. If null, header will only show if title or header slot content exists */
|
|
80
|
+
showHeader: { type: Boolean, default: null },
|
|
81
|
+
|
|
82
|
+
/** Title text to display in the panel header */
|
|
83
|
+
title: { type: String, default: null },
|
|
84
|
+
|
|
85
|
+
/** Additional CSS class to apply to the panel (use this instead of class because of the
|
|
86
|
+
* teleport) */
|
|
87
|
+
panelClass: { type: String, default: '' },
|
|
88
|
+
|
|
89
|
+
/** Additional CSS class to apply to the header element */
|
|
90
|
+
headerClass: { type: String, default: '' },
|
|
91
|
+
|
|
92
|
+
/** Whether the panel can be dragged by its header (or other element with
|
|
93
|
+
* data-tool-panel-drag="true") */
|
|
94
|
+
allowDrag: { type: Boolean, default: true },
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Boolean (true means all sides can be resized) or string containing N S E W characters to
|
|
98
|
+
* indicate which sides can be resized
|
|
99
|
+
*/
|
|
100
|
+
allowResize: { type: [String, Boolean], default: 'NSEW' },
|
|
101
|
+
|
|
102
|
+
/** Width of resize handles in pixels */
|
|
103
|
+
resizeHandleWidth: { type: Number, default: 8 },
|
|
104
|
+
|
|
105
|
+
/** Minimum width of the panel in pixels */
|
|
106
|
+
minWidth: { type: Number, default: null },
|
|
107
|
+
|
|
108
|
+
/** Minimum height of the panel in pixels */
|
|
109
|
+
minHeight: { type: Number, default: null },
|
|
110
|
+
},
|
|
111
|
+
data() {
|
|
112
|
+
const initialBounds = this.bounds ? { ...this.bounds } : null;
|
|
113
|
+
return {
|
|
114
|
+
ready: false,
|
|
115
|
+
localBounds: initialBounds,
|
|
116
|
+
containerSize: {
|
|
117
|
+
width: 0,
|
|
118
|
+
height: 0,
|
|
119
|
+
},
|
|
120
|
+
resizeObserver: null,
|
|
121
|
+
dragHandler: null,
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
computed: {
|
|
125
|
+
cssPosition() {
|
|
126
|
+
if (this.validBounds) {
|
|
127
|
+
const { x, y, width, height } = this.validBounds;
|
|
128
|
+
return {
|
|
129
|
+
left: x != null ? this.cssPx(x) : 'auto',
|
|
130
|
+
top: y != null ? this.cssPx(y) : 'auto',
|
|
131
|
+
width: width != null ? this.cssPx(width) : 'auto',
|
|
132
|
+
height: height != null ? this.cssPx(height) : 'auto',
|
|
133
|
+
minWidth: this.minWidth ? this.cssPx(this.minWidth) : null,
|
|
134
|
+
minHeight: this.minHeight ? this.cssPx(this.minHeight) : null,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return null;
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
canResizeNorth() {
|
|
142
|
+
return (
|
|
143
|
+
this.allowResize === true ||
|
|
144
|
+
(typeof this.allowResize === 'string' && this.allowResize.includes('N'))
|
|
145
|
+
);
|
|
146
|
+
},
|
|
147
|
+
canResizeSouth() {
|
|
148
|
+
return (
|
|
149
|
+
this.allowResize === true ||
|
|
150
|
+
(typeof this.allowResize === 'string' && this.allowResize.includes('S'))
|
|
151
|
+
);
|
|
152
|
+
},
|
|
153
|
+
canResizeEast() {
|
|
154
|
+
return (
|
|
155
|
+
this.allowResize === true ||
|
|
156
|
+
(typeof this.allowResize === 'string' && this.allowResize.includes('E'))
|
|
157
|
+
);
|
|
158
|
+
},
|
|
159
|
+
canResizeWest() {
|
|
160
|
+
return (
|
|
161
|
+
this.allowResize === true ||
|
|
162
|
+
(typeof this.allowResize === 'string' && this.allowResize.includes('W'))
|
|
163
|
+
);
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
shouldShowHeader() {
|
|
167
|
+
if (this.showHeader === null) {
|
|
168
|
+
return Boolean(this.title || this.$slots.header);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return !!this.showHeader;
|
|
172
|
+
},
|
|
173
|
+
validBounds() {
|
|
174
|
+
// default validation constrains to parent/window bounds
|
|
175
|
+
const validated = this.validateBounds(this.localBounds);
|
|
176
|
+
return validated;
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
watch: {
|
|
180
|
+
bounds(nv) {
|
|
181
|
+
if (nv && Object.keys(nv).length > 0) {
|
|
182
|
+
this.localBounds = { ...nv };
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
validBounds(nv, ov) {
|
|
187
|
+
if (JSON.stringify(nv) !== JSON.stringify(ov)) {
|
|
188
|
+
this.$emit('update:bounds', nv);
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
mounted() {
|
|
193
|
+
// listen for window resize if teleported to body
|
|
194
|
+
this.addPanelDragHandler();
|
|
195
|
+
this.addPanelResizeHandlers();
|
|
196
|
+
this.addContainerResizeHandlers();
|
|
197
|
+
|
|
198
|
+
this.$nextTick(() => {
|
|
199
|
+
this.updateContainerSize();
|
|
200
|
+
this.localBounds = {
|
|
201
|
+
x: 0,
|
|
202
|
+
y: 0,
|
|
203
|
+
width: null,
|
|
204
|
+
height: null,
|
|
205
|
+
...(this.bounds || {}),
|
|
206
|
+
};
|
|
207
|
+
this.ready = true;
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
beforeDestroy() {
|
|
211
|
+
this.removeContainerResizeHandlers();
|
|
212
|
+
if (this.dragHandler) {
|
|
213
|
+
this.dragHandler.disabled = true;
|
|
214
|
+
this.dragHandler = null;
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
methods: {
|
|
218
|
+
updateBounds(newBounds) {
|
|
219
|
+
this.localBounds = {
|
|
220
|
+
...this.localBounds,
|
|
221
|
+
...newBounds,
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
validateBounds(bounds) {
|
|
226
|
+
if (!bounds) {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const validated = { ...bounds };
|
|
231
|
+
|
|
232
|
+
// Always ensure minimum dimensions
|
|
233
|
+
if (validated.width != null) {
|
|
234
|
+
validated.width = Math.max(validated.width, this.minWidth || 0);
|
|
235
|
+
}
|
|
236
|
+
if (validated.height != null) {
|
|
237
|
+
validated.height = Math.max(validated.height, this.minHeight || 0);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Constrain to container/window bounds
|
|
241
|
+
if (validated.x != null) {
|
|
242
|
+
validated.x = Math.max(
|
|
243
|
+
0,
|
|
244
|
+
Math.min(
|
|
245
|
+
validated.x,
|
|
246
|
+
this.containerSize.width - (validated.width || this.minWidth),
|
|
247
|
+
),
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
if (validated.y != null) {
|
|
251
|
+
validated.y = Math.max(
|
|
252
|
+
0,
|
|
253
|
+
Math.min(
|
|
254
|
+
validated.y,
|
|
255
|
+
this.containerSize.height - (validated.height || this.minHeight),
|
|
256
|
+
),
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return validated;
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
updateContainerSize() {
|
|
264
|
+
// window size
|
|
265
|
+
if (this.teleportToBody) {
|
|
266
|
+
this.containerSize = {
|
|
267
|
+
width: document.documentElement.clientWidth,
|
|
268
|
+
height: document.documentElement.clientHeight,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// parent size
|
|
273
|
+
else if (this.$el && this.$el.parentElement) {
|
|
274
|
+
const parentRect = this.$el.parentElement.getBoundingClientRect();
|
|
275
|
+
this.containerSize = {
|
|
276
|
+
width: parentRect.width,
|
|
277
|
+
height: parentRect.height,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// no parent or window
|
|
282
|
+
else {
|
|
283
|
+
this.containerSize = {
|
|
284
|
+
width: 0,
|
|
285
|
+
height: 0,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
addContainerResizeHandlers() {
|
|
291
|
+
if (this.teleportToBody) {
|
|
292
|
+
window.addEventListener('resize', this.updateContainerSize);
|
|
293
|
+
} else {
|
|
294
|
+
if (typeof ResizeObserver === 'undefined' || !this.$el || !this.$el.parentElement) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
this.resizeObserver = new ResizeObserver(entries => {
|
|
299
|
+
this.updateContainerSize();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Start observing the parent element
|
|
303
|
+
this.resizeObserver.observe(this.$el.parentElement);
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
removeContainerResizeHandlers() {
|
|
308
|
+
if (this.teleportToBody) {
|
|
309
|
+
window.removeEventListener('resize', this.handleWindowResize);
|
|
310
|
+
} else if (this.resizeObserver) {
|
|
311
|
+
this.resizeObserver.disconnect();
|
|
312
|
+
this.resizeObserver = null;
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
addPanelDragHandler() {
|
|
317
|
+
const panel = this.$refs.panel;
|
|
318
|
+
if (!panel) return;
|
|
319
|
+
|
|
320
|
+
const dragElement = panel.querySelector('[data-tool-panel-drag="true"]');
|
|
321
|
+
if (!dragElement) return;
|
|
322
|
+
|
|
323
|
+
let startBounds = null;
|
|
324
|
+
|
|
325
|
+
this.dragHandler = new Drag(dragElement, {
|
|
326
|
+
onStart: () => {
|
|
327
|
+
if (!this.allowDrag) return;
|
|
328
|
+
startBounds = { ...this.validBounds };
|
|
329
|
+
this.$emit('dragstart', startBounds);
|
|
330
|
+
},
|
|
331
|
+
onDrag: ev => {
|
|
332
|
+
if (!this.allowDrag) return;
|
|
333
|
+
if (!startBounds) return;
|
|
334
|
+
|
|
335
|
+
// Calculate new position
|
|
336
|
+
const newX = startBounds.x + ev.startDelta.x;
|
|
337
|
+
const newY = startBounds.y + ev.startDelta.y;
|
|
338
|
+
|
|
339
|
+
// get the panel's actual dimensions (explicity or content size)
|
|
340
|
+
const panelHeight = this.validBounds.height || panel.offsetHeight;
|
|
341
|
+
const panelWidth = this.validBounds.width || panel.offsetWidth;
|
|
342
|
+
|
|
343
|
+
// constrain to current window bounds
|
|
344
|
+
const constrainedX = Math.max(
|
|
345
|
+
0,
|
|
346
|
+
Math.min(newX, this.containerSize.width - panelWidth),
|
|
347
|
+
);
|
|
348
|
+
const constrainedY = Math.max(
|
|
349
|
+
0,
|
|
350
|
+
Math.min(newY, this.containerSize.height - panelHeight),
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
const newBounds = this.validateBounds({
|
|
354
|
+
...this.validBounds,
|
|
355
|
+
x: constrainedX,
|
|
356
|
+
y: constrainedY,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
this.localBounds = newBounds;
|
|
360
|
+
this.$emit('drag', newBounds);
|
|
361
|
+
},
|
|
362
|
+
onEnd: () => {
|
|
363
|
+
if (!this.allowDrag) return;
|
|
364
|
+
|
|
365
|
+
if (startBounds) {
|
|
366
|
+
this.$emit('dragend', this.validBounds);
|
|
367
|
+
}
|
|
368
|
+
startBounds = null;
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
addPanelResizeHandlers() {
|
|
374
|
+
const panel = this.$refs.panel;
|
|
375
|
+
if (!panel) return;
|
|
376
|
+
|
|
377
|
+
const handles = panel.querySelectorAll('[data-resize-handle]');
|
|
378
|
+
if (!handles.length) return;
|
|
379
|
+
|
|
380
|
+
handles.forEach(handle => {
|
|
381
|
+
const direction = handle.getAttribute('data-resize-handle');
|
|
382
|
+
const isEast = direction.includes('e');
|
|
383
|
+
const isWest = direction.includes('w');
|
|
384
|
+
const isNorth = direction.includes('n');
|
|
385
|
+
const isSouth = direction.includes('s');
|
|
386
|
+
let startBounds = null;
|
|
387
|
+
|
|
388
|
+
new Drag(handle, {
|
|
389
|
+
onStart: () => {
|
|
390
|
+
startBounds = { ...this.validBounds };
|
|
391
|
+
if (startBounds.height == null) {
|
|
392
|
+
startBounds.height = panel.offsetHeight;
|
|
393
|
+
}
|
|
394
|
+
if (startBounds.width == null) {
|
|
395
|
+
startBounds.width = panel.offsetWidth;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
this.$emit('resizestart', startBounds);
|
|
399
|
+
},
|
|
400
|
+
onDrag: ev => {
|
|
401
|
+
if (!startBounds) return;
|
|
402
|
+
|
|
403
|
+
const newBounds = { ...startBounds };
|
|
404
|
+
const minX = 0;
|
|
405
|
+
const minY = 0;
|
|
406
|
+
const maxX = this.containerSize.width;
|
|
407
|
+
const maxY = this.containerSize.height;
|
|
408
|
+
|
|
409
|
+
// Handle horizontal resizing
|
|
410
|
+
if (isEast || isWest) {
|
|
411
|
+
if (isWest) {
|
|
412
|
+
// Resizing from left edge
|
|
413
|
+
const newX = Math.max(minX, startBounds.x + ev.startDelta.x);
|
|
414
|
+
const maxWidth = startBounds.x + startBounds.width - newX;
|
|
415
|
+
newBounds.x = newX;
|
|
416
|
+
newBounds.width = Math.min(
|
|
417
|
+
startBounds.width - (newX - startBounds.x),
|
|
418
|
+
maxWidth,
|
|
419
|
+
);
|
|
420
|
+
} else {
|
|
421
|
+
// Resizing from right edge
|
|
422
|
+
const maxWidth = maxX - startBounds.x;
|
|
423
|
+
newBounds.width = Math.min(
|
|
424
|
+
startBounds.width + ev.startDelta.x,
|
|
425
|
+
maxWidth,
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Handle vertical resizing
|
|
431
|
+
if (isNorth || isSouth) {
|
|
432
|
+
if (isNorth) {
|
|
433
|
+
// Resizing from top edge
|
|
434
|
+
const newY = Math.max(minY, startBounds.y + ev.startDelta.y);
|
|
435
|
+
const maxHeight = startBounds.y + startBounds.height - newY;
|
|
436
|
+
newBounds.y = newY;
|
|
437
|
+
newBounds.height = Math.min(
|
|
438
|
+
startBounds.height - (newY - startBounds.y),
|
|
439
|
+
maxHeight,
|
|
440
|
+
);
|
|
441
|
+
} else {
|
|
442
|
+
// Resizing from bottom edge
|
|
443
|
+
const maxHeight = maxY - startBounds.y;
|
|
444
|
+
newBounds.height = Math.min(
|
|
445
|
+
startBounds.height + ev.startDelta.y,
|
|
446
|
+
maxHeight,
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Only apply minimum dimensions to non-null values
|
|
452
|
+
if (newBounds.width != null) {
|
|
453
|
+
newBounds.width = Math.max(newBounds.width, this.minWidth || 0);
|
|
454
|
+
}
|
|
455
|
+
if (newBounds.height != null) {
|
|
456
|
+
newBounds.height = Math.max(newBounds.height, this.minHeight || 0);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
this.updateBounds(newBounds);
|
|
460
|
+
this.$emit('resize', this.validBounds);
|
|
461
|
+
},
|
|
462
|
+
onEnd: () => {
|
|
463
|
+
if (startBounds) {
|
|
464
|
+
this.$emit('resizeend', this.validBounds);
|
|
465
|
+
}
|
|
466
|
+
startBounds = null;
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
},
|
|
471
|
+
getPanelElement() {
|
|
472
|
+
return this.$refs.panel;
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
};
|
|
476
|
+
</script>
|
|
477
|
+
|
|
478
|
+
<style lang="scss">
|
|
479
|
+
@use '../styles/px.scss' as *;
|
|
480
|
+
|
|
481
|
+
.px-tool-panel {
|
|
482
|
+
// panel
|
|
483
|
+
--px-tool-panel-bg: #{get('controls.popup.background-color')};
|
|
484
|
+
--px-tool-panel-padding: 0.75em;
|
|
485
|
+
--px-tool-panel-radius: #{get('controls.popup.border-radius')};
|
|
486
|
+
--px-tool-panel-shadow: #{get('controls.popup.box-shadow')};
|
|
487
|
+
|
|
488
|
+
// header
|
|
489
|
+
--px-tool-panel-header-bg: transparent;
|
|
490
|
+
--px-tool-panel-header-padding: 0.5em 0.75em;
|
|
491
|
+
--px-tool-panel-header-border-color: transparent;
|
|
492
|
+
|
|
493
|
+
// resize
|
|
494
|
+
--px-tool-panel-resize-width: 8px;
|
|
495
|
+
--px-tool-panel-resize-handle-hover-bg: #{rgba(0, 0, 0, 0)};
|
|
496
|
+
|
|
497
|
+
//--px-tool-panel-bg: var(--theme-page-bg, rgba(255, 255, 255, 0.9));
|
|
498
|
+
|
|
499
|
+
position: absolute;
|
|
500
|
+
border-radius: var(--px-tool-panel-radius);
|
|
501
|
+
box-shadow: var(--px-tool-panel-shadow);
|
|
502
|
+
background-color: var(--px-tool-panel-bg);
|
|
503
|
+
box-sizing: border-box;
|
|
504
|
+
display: flex;
|
|
505
|
+
flex-direction: column;
|
|
506
|
+
|
|
507
|
+
&--teleport {
|
|
508
|
+
position: fixed;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
&__header {
|
|
512
|
+
padding: var(--px-tool-panel-header-padding);
|
|
513
|
+
background-color: var(--px-tool-panel-header-bg);
|
|
514
|
+
border-bottom: 1px solid var(--px-tool-panel-header-border-color);
|
|
515
|
+
user-select: none;
|
|
516
|
+
display: flex;
|
|
517
|
+
align-items: center;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
[data-tool-panel-drag='true'] {
|
|
521
|
+
cursor: move;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
&__content {
|
|
525
|
+
position: relative;
|
|
526
|
+
flex: 1;
|
|
527
|
+
min-height: 0;
|
|
528
|
+
padding: var(--px-tool-panel-padding);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
&__resize-handle {
|
|
532
|
+
position: absolute;
|
|
533
|
+
background-color: transparent;
|
|
534
|
+
z-index: 1;
|
|
535
|
+
|
|
536
|
+
&--n {
|
|
537
|
+
top: calc(-1 * var(--px-tool-panel-resize-width) / 2);
|
|
538
|
+
left: 0;
|
|
539
|
+
right: 0;
|
|
540
|
+
height: var(--px-tool-panel-resize-width);
|
|
541
|
+
cursor: ns-resize;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
&--s {
|
|
545
|
+
bottom: calc(-1 * var(--px-tool-panel-resize-width) / 2);
|
|
546
|
+
left: 0;
|
|
547
|
+
right: 0;
|
|
548
|
+
height: var(--px-tool-panel-resize-width);
|
|
549
|
+
cursor: ns-resize;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
&--e {
|
|
553
|
+
top: 0;
|
|
554
|
+
right: calc(-1 * var(--px-tool-panel-resize-width) / 2);
|
|
555
|
+
bottom: 0;
|
|
556
|
+
width: var(--px-tool-panel-resize-width);
|
|
557
|
+
cursor: ew-resize;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
&--w {
|
|
561
|
+
top: 0;
|
|
562
|
+
left: calc(-1 * var(--px-tool-panel-resize-width) / 2);
|
|
563
|
+
bottom: 0;
|
|
564
|
+
width: var(--px-tool-panel-resize-width);
|
|
565
|
+
cursor: ew-resize;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/* Corner handles - small squares */
|
|
569
|
+
&--ne {
|
|
570
|
+
top: calc(-1 * var(--px-tool-panel-resize-width) / 2);
|
|
571
|
+
right: calc(-1 * var(--px-tool-panel-resize-width) / 2);
|
|
572
|
+
width: var(--px-tool-panel-resize-width);
|
|
573
|
+
height: var(--px-tool-panel-resize-width);
|
|
574
|
+
cursor: nesw-resize;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
&--nw {
|
|
578
|
+
top: calc(-1 * var(--px-tool-panel-resize-width) / 2);
|
|
579
|
+
left: calc(-1 * var(--px-tool-panel-resize-width) / 2);
|
|
580
|
+
width: var(--px-tool-panel-resize-width);
|
|
581
|
+
height: var(--px-tool-panel-resize-width);
|
|
582
|
+
cursor: nesw-resize;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
&--se {
|
|
586
|
+
bottom: calc(-1 * var(--px-tool-panel-resize-width) / 2);
|
|
587
|
+
right: calc(-1 * var(--px-tool-panel-resize-width) / 2);
|
|
588
|
+
width: var(--px-tool-panel-resize-width);
|
|
589
|
+
height: var(--px-tool-panel-resize-width);
|
|
590
|
+
cursor: nwse-resize;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
&--sw {
|
|
594
|
+
bottom: calc(-1 * var(--px-tool-panel-resize-width) / 2);
|
|
595
|
+
left: calc(-1 * var(--px-tool-panel-resize-width) / 2);
|
|
596
|
+
width: var(--px-tool-panel-resize-width);
|
|
597
|
+
height: var(--px-tool-panel-resize-width);
|
|
598
|
+
cursor: nesw-resize;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
&:hover {
|
|
602
|
+
background-color: var(--px-tool-panel-resize-handle-hover-bg);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
</style>
|
|
@@ -15,18 +15,37 @@ import { deepMerge } from '../utils/utils.js';
|
|
|
15
15
|
* }
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
export default function useSettings(
|
|
18
|
+
export default function useSettings(
|
|
19
|
+
key,
|
|
20
|
+
defaultSettings = {},
|
|
21
|
+
beforeInit = null,
|
|
22
|
+
afterInit = null,
|
|
23
|
+
) {
|
|
19
24
|
const settings = ref(defaultSettings);
|
|
20
25
|
|
|
21
26
|
const loadSettings = () => {
|
|
22
27
|
if (process.client) {
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
let parsed = {};
|
|
29
|
+
try {
|
|
30
|
+
const stored = localStorage.getItem(key);
|
|
31
|
+
parsed = stored ? JSON.parse(stored) : {};
|
|
32
|
+
|
|
33
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
34
|
+
parsed = {};
|
|
35
|
+
}
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error('Error loading settings:', e);
|
|
38
|
+
parsed = {};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (beforeInit && typeof beforeInit === 'function') {
|
|
42
|
+
beforeInit(settings, parsed);
|
|
43
|
+
}
|
|
25
44
|
|
|
26
45
|
settings.value = deepMerge(settings.value, parsed, true);
|
|
27
46
|
|
|
28
|
-
if (
|
|
29
|
-
|
|
47
|
+
if (afterInit && typeof afterInit === 'function') {
|
|
48
|
+
afterInit(settings);
|
|
30
49
|
}
|
|
31
50
|
}
|
|
32
51
|
};
|