@thinkpixellab-public/px-vue 3.0.95 → 3.0.96
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/PxFit.vue +142 -139
- package/components/PxProgressCircle.vue +68 -10
- package/components/PxSelectionBar.vue +132 -0
- package/package.json +1 -1
package/components/PxFit.vue
CHANGED
|
@@ -1,24 +1,10 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
|
|
3
|
-
<px-fit :fits="['lg', 'md', 'sm']" :debug="true">
|
|
4
|
-
<template #default="{ fit }">
|
|
5
|
-
<div :class="`fit fit--${fit}`">
|
|
6
|
-
<span v-if="fit == 'lg'">LARGE</span>
|
|
7
|
-
<span v-if="fit == 'md'">MEDIUM</span>
|
|
8
|
-
<span v-if="fit == 'sm'">SMALL</span>
|
|
9
|
-
One Two Three Four Five Size Seven Eight Nine Ten
|
|
10
|
-
</div>
|
|
11
|
-
</template>
|
|
12
|
-
</px-fit>
|
|
13
|
-
|
|
14
|
-
-->
|
|
15
1
|
<template>
|
|
16
2
|
<div :class="bem({ debug: debug, fitting: fitting })">
|
|
17
|
-
<div ref="outer" :class="bem('outer')" :style="{ opacity:
|
|
3
|
+
<div ref="outer" :class="bem('outer')" :style="{ opacity: contentOpacity }">
|
|
18
4
|
<slot
|
|
19
5
|
name="default"
|
|
20
6
|
v-bind="{
|
|
21
|
-
fit: fit,
|
|
7
|
+
fit: fitting ? fit : selectedFit,
|
|
22
8
|
fitting: fitting,
|
|
23
9
|
}"
|
|
24
10
|
/>
|
|
@@ -28,37 +14,89 @@
|
|
|
28
14
|
|
|
29
15
|
<script>
|
|
30
16
|
import PxBaseResize from './PxBaseResize.vue';
|
|
17
|
+
import { asyncTimeout } from '../utils/utils.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A flexibile container that allows you to choose different possible renderings based on the size
|
|
21
|
+
* of the container. You provide a collection of "fits" (essentially just tags). Each tag is
|
|
22
|
+
* provided to the default slot template and the template can determine how to render based on that
|
|
23
|
+
* tag. Once rendered, the content is measured and compared against the size of the parent and the
|
|
24
|
+
* first that "fits" within the bounds of the parent is selected.
|
|
25
|
+
*
|
|
26
|
+
* ```html
|
|
27
|
+
* <PxFit>
|
|
28
|
+
* <template #default="{ fit }">
|
|
29
|
+
* <div >
|
|
30
|
+
* <div v-if="fit == 'lg'" style="width: 600px">lg (600px)</div>
|
|
31
|
+
* <div v-if="fit == 'md'" style="width: 400px">md (400px)</div>
|
|
32
|
+
* <div v-if="fit == 'sm'" style="width: 200px">sm (200px)</div>
|
|
33
|
+
* </div>
|
|
34
|
+
* </template>
|
|
35
|
+
* </PxFit>
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
31
38
|
|
|
32
39
|
export default {
|
|
33
40
|
name: 'px-fit',
|
|
34
41
|
mixins: [PxBaseResize],
|
|
35
42
|
props: {
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
/**
|
|
44
|
+
* A list of named "fits" that will be applied during resize. These have no intrinsic size
|
|
45
|
+
* or meaning. Instead it is up to the template to use these to modify the size of the
|
|
46
|
+
* content. PxFit will apply each named fit from largest to smallest (by default in reverse
|
|
47
|
+
* order of what is listed) and keep the first that fits within the bounds of the element.
|
|
48
|
+
*
|
|
49
|
+
*/
|
|
38
50
|
fits: { type: Array, default: () => ['sm', 'md', 'lg'] },
|
|
39
51
|
|
|
40
|
-
|
|
41
|
-
|
|
52
|
+
/**
|
|
53
|
+
* By default, fits will be tested in reverse order since it's natural to list them from
|
|
54
|
+
* smallest to largest but they oftend need to be tested largest first (assuming we want to
|
|
55
|
+
* show as much content as possible). To disable this behavior, set this to false.
|
|
56
|
+
*/
|
|
57
|
+
reverseFitTestOrder: { type: Boolean, default: true },
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* By default the last fit attempted will be kept when no fit is found, but you can set this
|
|
61
|
+
* to specific a different fallback. Note that fallback does not need to be a valid fit from
|
|
62
|
+
* the fits collection, so you could set it to something like 'none' and use that to flag
|
|
63
|
+
* that no fit was found and you need to render an alternative.
|
|
64
|
+
*/
|
|
42
65
|
fallback: { type: String, default: null },
|
|
43
66
|
|
|
44
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Whether to evaluate width, height or both when looking for a fit
|
|
69
|
+
*/
|
|
45
70
|
mode: { type: String, default: 'both' },
|
|
46
71
|
|
|
47
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Set this to something other than 0 to debounce resize events.
|
|
74
|
+
*/
|
|
75
|
+
resizeDebounce: { type: Number, default: 0 },
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* If true, we set the opacity of the content to 0 while fitting is in progress. Doesn't
|
|
79
|
+
* seem like this is necessary is most cases.
|
|
80
|
+
*/
|
|
81
|
+
hideFitTests: { type: Boolean, default: true },
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Use this to override the function that evaluates whether a particular fit is valid. Takes
|
|
85
|
+
* the current mode, available size, content size (with fit applied) and fit as arguments
|
|
86
|
+
* and should return a boolean.
|
|
87
|
+
*/
|
|
88
|
+
fitTestFn: { default: null },
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Disables resize events (and fitting) when true
|
|
92
|
+
*/
|
|
48
93
|
disabled: { type: Boolean, default: false },
|
|
49
94
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// whether to show the debug version of the component
|
|
95
|
+
/**
|
|
96
|
+
* Whether to show the debug version of the component (allows you to see the component in
|
|
97
|
+
* each fit state as it is evaluated).
|
|
98
|
+
*/
|
|
56
99
|
debug: { type: Boolean, default: false },
|
|
57
|
-
|
|
58
|
-
// override the function that evaluates whether a particular fit is valid (called after the
|
|
59
|
-
// component has been placed into the "fit" state, takes the current mode, available
|
|
60
|
-
// outer size and fit being evaluated as parameters and should return true or false)
|
|
61
|
-
testFitFunction: { default: null },
|
|
62
100
|
},
|
|
63
101
|
|
|
64
102
|
data() {
|
|
@@ -66,23 +104,20 @@ export default {
|
|
|
66
104
|
fit: null,
|
|
67
105
|
selectedFit: null,
|
|
68
106
|
fitting: false,
|
|
69
|
-
|
|
70
|
-
outerSize: { w: 0, h: 0 },
|
|
107
|
+
resizeId: 0,
|
|
71
108
|
};
|
|
72
109
|
},
|
|
73
110
|
|
|
74
111
|
computed: {
|
|
75
112
|
resizeDebounceValue() {
|
|
76
|
-
return
|
|
113
|
+
return this.resizeDebounce;
|
|
77
114
|
},
|
|
78
115
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
map.fitting = this.fitting;
|
|
85
|
-
return map;
|
|
116
|
+
contentOpacity() {
|
|
117
|
+
if (this.hideFitTests && this.fitting) {
|
|
118
|
+
return this.debug ? 0.75 : 0;
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
86
121
|
},
|
|
87
122
|
},
|
|
88
123
|
|
|
@@ -93,129 +128,97 @@ export default {
|
|
|
93
128
|
},
|
|
94
129
|
|
|
95
130
|
methods: {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const outerSize = {
|
|
101
|
-
w: outer.offsetWidth,
|
|
102
|
-
h: outer.offsetHeight,
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
let doesFit = true;
|
|
106
|
-
|
|
107
|
-
if (mode == 'width' || mode == 'both') {
|
|
108
|
-
doesFit = doesFit && available.w >= outerSize.w;
|
|
109
|
-
}
|
|
110
|
-
if (mode == 'height' || mode == 'both') {
|
|
111
|
-
doesFit = doesFit && available.h >= outerSize.h;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (this.debug) {
|
|
115
|
-
console.log(`${fit}: ${doesFit}`);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return doesFit;
|
|
131
|
+
async wait() {
|
|
132
|
+
await this.$nextTick();
|
|
133
|
+
if (this.debug) {
|
|
134
|
+
await asyncTimeout(1000);
|
|
119
135
|
}
|
|
120
|
-
return false;
|
|
121
136
|
},
|
|
122
137
|
|
|
123
|
-
|
|
124
|
-
if (
|
|
125
|
-
return;
|
|
138
|
+
testFitDefault(mode, availableSize, contentSize, fit) {
|
|
139
|
+
if (!availableSize || !contentSize || availableSize.w == 0 || availableSize.h == 0) {
|
|
140
|
+
return false;
|
|
126
141
|
}
|
|
127
142
|
|
|
128
|
-
|
|
129
|
-
// on the next frame, check the size
|
|
130
|
-
this.fit = fit;
|
|
131
|
-
|
|
132
|
-
let timeout;
|
|
133
|
-
|
|
134
|
-
const abortListener = () => {
|
|
135
|
-
if (timeout) {
|
|
136
|
-
clearTimeout(timeout);
|
|
137
|
-
}
|
|
138
|
-
signal.removeEventListener('abort', abortListener);
|
|
139
|
-
reject();
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
signal.addEventListener('abort', abortListener);
|
|
143
|
+
let doesFit = true;
|
|
143
144
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const testFn = this.testFitFunction || this.testFitDefault;
|
|
149
|
-
const doesFit = testFn(this.mode, this.availableSize, fit);
|
|
150
|
-
|
|
151
|
-
resolve(doesFit);
|
|
152
|
-
},
|
|
153
|
-
this.debug ? 1000 : this.restoreDelay
|
|
154
|
-
);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
return promise;
|
|
158
|
-
},
|
|
145
|
+
if (mode == 'width' || mode == 'both') {
|
|
146
|
+
doesFit = doesFit && availableSize.w >= contentSize.w;
|
|
147
|
+
}
|
|
159
148
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
this.aborter.abort();
|
|
149
|
+
if (mode == 'height' || mode == 'both') {
|
|
150
|
+
doesFit = doesFit && availableSize.h >= contentSize.h;
|
|
163
151
|
}
|
|
164
152
|
|
|
165
|
-
|
|
166
|
-
|
|
153
|
+
return doesFit;
|
|
154
|
+
},
|
|
167
155
|
|
|
168
|
-
|
|
169
|
-
|
|
156
|
+
async tryFits(id) {
|
|
157
|
+
const availableSize = {
|
|
170
158
|
w: this.$el.clientWidth,
|
|
171
159
|
h: this.$el.clientHeight,
|
|
172
160
|
};
|
|
173
161
|
|
|
174
|
-
// places the component into a state where it can be sized independently
|
|
175
162
|
this.fitting = true;
|
|
176
163
|
|
|
164
|
+
const $outer = this.$refs.outer;
|
|
165
|
+
const orderedFits = this.reverseFitTestOrder ? this.fits.slice().reverse() : this.fits;
|
|
166
|
+
const testFn = this.fitTestFn || this.testFitDefault;
|
|
167
|
+
|
|
177
168
|
let fit;
|
|
169
|
+
let selectedFit;
|
|
178
170
|
|
|
179
|
-
for (let i =
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
try {
|
|
183
|
-
const doesFit = await this.tryFit(fit, signal);
|
|
184
|
-
if (doesFit) {
|
|
185
|
-
// we found a fit so clean up and return
|
|
186
|
-
this.selectedFit = fit;
|
|
187
|
-
this.aborter = null;
|
|
188
|
-
this.abortSignal = null;
|
|
189
|
-
this.fitting = false;
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
} catch {
|
|
193
|
-
// aborted
|
|
171
|
+
for (let i = 0; i < orderedFits.length; i++) {
|
|
172
|
+
// we can abort the test if another has been started
|
|
173
|
+
if (id != this.resizeId) {
|
|
194
174
|
return;
|
|
195
175
|
}
|
|
196
|
-
}
|
|
197
176
|
|
|
198
|
-
|
|
177
|
+
fit = orderedFits[i];
|
|
178
|
+
this.fit = fit;
|
|
179
|
+
|
|
180
|
+
await this.wait();
|
|
181
|
+
|
|
182
|
+
// need to test his again after the wait
|
|
183
|
+
if (id != this.resizeId) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const contentSize = {
|
|
188
|
+
w: $outer.offsetWidth,
|
|
189
|
+
h: $outer.offsetHeight,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const doesFit = testFn(this.mode, availableSize, contentSize, fit);
|
|
199
193
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
194
|
+
if (this.debug) {
|
|
195
|
+
console.log(`----- ${id} Test ${this.fit} -----`);
|
|
196
|
+
console.log(`availableSize: ${availableSize.w} x ${availableSize.h}`);
|
|
197
|
+
console.log(`contentSize: ${contentSize.w} x ${contentSize.h}`);
|
|
198
|
+
console.log(`doesFit: ${doesFit}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (doesFit) {
|
|
202
|
+
selectedFit = fit;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
this.selectedFit = selectedFit ? selectedFit : this.fallback || fit;
|
|
205
208
|
this.fitting = false;
|
|
206
209
|
},
|
|
207
210
|
|
|
208
|
-
resizeStarted() {
|
|
209
|
-
this.$emit('resize-start');
|
|
210
|
-
},
|
|
211
211
|
async resize() {
|
|
212
212
|
if (!this.disabled) {
|
|
213
|
-
this
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
213
|
+
this.$emit('resize-start');
|
|
214
|
+
|
|
215
|
+
this.resizeId++;
|
|
216
|
+
|
|
217
|
+
if (this.debug) {
|
|
218
|
+
console.log(`===== RESIZE ${this.resizeId} =====`);
|
|
219
|
+
}
|
|
217
220
|
|
|
218
|
-
await this.
|
|
221
|
+
await this.tryFits(this.resizeId);
|
|
219
222
|
|
|
220
223
|
this.$emit('resize-end');
|
|
221
224
|
}
|
|
@@ -260,7 +263,7 @@ export default {
|
|
|
260
263
|
width: auto;
|
|
261
264
|
height: auto;
|
|
262
265
|
pointer-events: none;
|
|
263
|
-
opacity: 0;
|
|
266
|
+
//opacity: 0;
|
|
264
267
|
}
|
|
265
268
|
}
|
|
266
269
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
-->
|
|
8
8
|
<template>
|
|
9
|
-
<div :class="bem()"
|
|
9
|
+
<div :class="bem()">
|
|
10
10
|
<svg
|
|
11
11
|
width="100%"
|
|
12
12
|
height="100%"
|
|
@@ -16,13 +16,26 @@
|
|
|
16
16
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
17
17
|
>
|
|
18
18
|
<circle
|
|
19
|
+
v-if="backgroundVisible"
|
|
20
|
+
:class="bem('bg')"
|
|
19
21
|
stroke="currentColor"
|
|
20
|
-
:stroke-width="
|
|
22
|
+
:stroke-width="calcStrokeWidth"
|
|
23
|
+
:stroke-linecap="rounded ? 'round' : null"
|
|
24
|
+
fill="transparent"
|
|
25
|
+
:cx="width ? width / 2 : 0"
|
|
26
|
+
:cy="width ? width / 2 : 0"
|
|
27
|
+
:r="width ? Math.max(0, width / 2 - calcStrokeWidth / 2) : 0"
|
|
28
|
+
></circle>
|
|
29
|
+
<circle
|
|
30
|
+
:class="bem('fg')"
|
|
31
|
+
stroke="currentColor"
|
|
32
|
+
:stroke-width="calcStrokeWidth"
|
|
21
33
|
:stroke-dasharray="strokeDashArray"
|
|
34
|
+
:stroke-linecap="rounded ? 'round' : null"
|
|
22
35
|
fill="transparent"
|
|
23
36
|
:cx="width ? width / 2 : 0"
|
|
24
37
|
:cy="width ? width / 2 : 0"
|
|
25
|
-
:r="width ? Math.max(0, width / 2 -
|
|
38
|
+
:r="width ? Math.max(0, width / 2 - calcStrokeWidth / 2) : 0"
|
|
26
39
|
></circle>
|
|
27
40
|
</svg>
|
|
28
41
|
</div>
|
|
@@ -30,26 +43,49 @@
|
|
|
30
43
|
|
|
31
44
|
<script>
|
|
32
45
|
import PxBase from './PxBase.vue';
|
|
46
|
+
import PxBaseResize from '@thinkpixellab-public/px-vue/components/PxBaseResize.vue';
|
|
47
|
+
import tween from '../utils/tween.js';
|
|
33
48
|
|
|
34
49
|
export default {
|
|
35
50
|
name: 'progress-circle',
|
|
36
|
-
mixins: [PxBase],
|
|
51
|
+
mixins: [PxBase, PxBaseResize],
|
|
37
52
|
props: {
|
|
38
|
-
|
|
53
|
+
// if stroke width is a string with a percentage it will be calculated as a percentage of the total width
|
|
54
|
+
strokeWidth: { type: [Number, String], default: 10 },
|
|
39
55
|
progress: { type: Number, default: 0 },
|
|
56
|
+
rounded: { type: Boolean, default: false },
|
|
57
|
+
backgroundVisible: { type: Boolean, default: false },
|
|
40
58
|
},
|
|
41
59
|
data() {
|
|
42
|
-
return { width: 0, p: 0 };
|
|
60
|
+
return { width: 0, p: 0, animatedProgress: this.progress };
|
|
43
61
|
},
|
|
44
62
|
computed: {
|
|
45
63
|
strokeDashArray() {
|
|
46
|
-
let c = (this.width - this.
|
|
47
|
-
return `${c * Math.max(0, Math.min(1, this.
|
|
64
|
+
let c = (this.width - this.calcStrokeWidth) * Math.PI;
|
|
65
|
+
return `${c * Math.max(0, Math.min(1, this.animatedProgress))},${c}`;
|
|
66
|
+
},
|
|
67
|
+
calcStrokeWidth() {
|
|
68
|
+
if (typeof this.strokeWidth === 'string' && this.strokeWidth.endsWith('%')) {
|
|
69
|
+
return (parseFloat(this.strokeWidth) / 100) * this.width;
|
|
70
|
+
}
|
|
71
|
+
return this.strokeWidth;
|
|
48
72
|
},
|
|
49
73
|
},
|
|
50
|
-
|
|
51
|
-
|
|
74
|
+
watch: {
|
|
75
|
+
progress(nv) {
|
|
76
|
+
if (this.progressTween) {
|
|
77
|
+
this.progressTween.cancel();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.progressTween = tween({
|
|
81
|
+
from: this.animatedProgress,
|
|
82
|
+
to: nv,
|
|
83
|
+
dur: 1000,
|
|
84
|
+
update: v => (this.animatedProgress = v),
|
|
85
|
+
});
|
|
86
|
+
},
|
|
52
87
|
},
|
|
88
|
+
|
|
53
89
|
methods: {
|
|
54
90
|
resize() {
|
|
55
91
|
this.width = this.$el.getBoundingClientRect()?.width;
|
|
@@ -61,13 +97,35 @@ export default {
|
|
|
61
97
|
<style lang="scss">
|
|
62
98
|
@use '../styles/px.scss' as *;
|
|
63
99
|
.progress-circle {
|
|
100
|
+
width: 100%;
|
|
101
|
+
height: 100%;
|
|
102
|
+
|
|
103
|
+
@at-root {
|
|
104
|
+
:root {
|
|
105
|
+
--px-circle-progress-bg: currentColor;
|
|
106
|
+
--px-circle-progress-bg-alpha: 0.1;
|
|
107
|
+
--px-circle-progress-fg: currentColor;
|
|
108
|
+
--px-circle-progress-alpha: 1;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
64
112
|
aspect-ratio: 1;
|
|
65
113
|
font-size: 0;
|
|
114
|
+
|
|
66
115
|
svg {
|
|
67
116
|
width: 100%;
|
|
68
117
|
height: 100%;
|
|
69
118
|
display: block;
|
|
70
119
|
transform: rotate(-90deg);
|
|
71
120
|
}
|
|
121
|
+
|
|
122
|
+
&__bg {
|
|
123
|
+
stroke: var(--px-circle-progress-bg, currentColor);
|
|
124
|
+
opacity: var(--px-circle-progress-bg-alpha, 0.2);
|
|
125
|
+
}
|
|
126
|
+
&__fg {
|
|
127
|
+
stroke: var(--px-circle-progress-fg, currentColor);
|
|
128
|
+
opacity: var(--px-circle-progress-fg-alpha, 1);
|
|
129
|
+
}
|
|
72
130
|
}
|
|
73
131
|
</style>
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Describe this component...
|
|
3
|
+
-->
|
|
4
|
+
<template>
|
|
5
|
+
<div :class="bem()">
|
|
6
|
+
<div :class="bem('selector-container', {})" :style="selectorStyle">
|
|
7
|
+
<div :class="[bem('selector'), selectorClass]"></div>
|
|
8
|
+
</div>
|
|
9
|
+
<slot />
|
|
10
|
+
</div>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script>
|
|
14
|
+
import PxBaseResize from './PxBaseResize.vue';
|
|
15
|
+
import utils from '../utils/utils';
|
|
16
|
+
|
|
17
|
+
// import { mapState } from 'vuex';
|
|
18
|
+
// import Component from '~/components/Component.vue';
|
|
19
|
+
// import Mixin from '~/components/Mixin.vue';
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
name: 'px-selection-bar',
|
|
23
|
+
// components: { Component },
|
|
24
|
+
mixins: [PxBaseResize],
|
|
25
|
+
props: {
|
|
26
|
+
// above, below, start | left, end | right
|
|
27
|
+
placement: { type: String, default: 'below' },
|
|
28
|
+
|
|
29
|
+
selectorClass: { type: String, default: null },
|
|
30
|
+
|
|
31
|
+
selectedSelector: { type: String, default: '[data-selected]' },
|
|
32
|
+
|
|
33
|
+
selectorThickness: { type: Number, default: 2 },
|
|
34
|
+
|
|
35
|
+
// used only to create a watcher for when selection changes, the actual placement of the
|
|
36
|
+
// selection is handled by finding the selected element using selectedSelector
|
|
37
|
+
selectedIndex: { type: Number, default: null },
|
|
38
|
+
},
|
|
39
|
+
data() {
|
|
40
|
+
return { selectionOffset: 0, selectionSize: 100 };
|
|
41
|
+
},
|
|
42
|
+
computed: {
|
|
43
|
+
calcPlacement() {
|
|
44
|
+
if (this.placement == 'start' || this.placement == 'left') {
|
|
45
|
+
return 'start';
|
|
46
|
+
} else if (this.placement == 'end' || this.placement == 'right') {
|
|
47
|
+
return 'end';
|
|
48
|
+
} else {
|
|
49
|
+
return this.placement;
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
isHorizontal() {
|
|
53
|
+
return this.calcPlacement == 'above' || this.calcPlacement == 'below';
|
|
54
|
+
},
|
|
55
|
+
isVertical() {
|
|
56
|
+
return !this.isHorizontal;
|
|
57
|
+
},
|
|
58
|
+
selectorStyle() {
|
|
59
|
+
const style = {};
|
|
60
|
+
|
|
61
|
+
style[this.isHorizontal ? 'left' : 'top'] = this.cssPx(this.selectionOffset);
|
|
62
|
+
style[this.isHorizontal ? 'width' : 'height'] = this.cssPx(this.selectionSize);
|
|
63
|
+
|
|
64
|
+
if (this.isHorizontal) {
|
|
65
|
+
style.top = this.calcPlacement == 'above' ? 0 : 'auto';
|
|
66
|
+
style.bottom = this.calcPlacement == 'below' ? 0 : 'auto';
|
|
67
|
+
style.height = this.cssPx(this.selectorThickness);
|
|
68
|
+
} else {
|
|
69
|
+
style.left = this.calcPlacement == 'start' ? 0 : 'auto';
|
|
70
|
+
style.right = this.calcPlacement == 'end' ? 0 : 'auto';
|
|
71
|
+
style.width = this.cssPx(this.selectorThickness);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return style;
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
watch: {
|
|
78
|
+
selectionPlacement() {
|
|
79
|
+
this.$nextTick(() => {
|
|
80
|
+
this.updateSelection();
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
selectedIndex() {
|
|
84
|
+
this.$nextTick(() => {
|
|
85
|
+
this.updateSelection();
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
mounted() {
|
|
90
|
+
this.updateSelection();
|
|
91
|
+
},
|
|
92
|
+
methods: {
|
|
93
|
+
resize() {
|
|
94
|
+
this.updateSelection();
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
updateSelection() {
|
|
98
|
+
const selected = this.$el.querySelector(this.selectedSelector);
|
|
99
|
+
if (selected) {
|
|
100
|
+
const rect = utils.getClientRect(selected, this.$el);
|
|
101
|
+
//const rect = selected.getBoundingClientRect();
|
|
102
|
+
if (this.isHorizontal) {
|
|
103
|
+
this.selectionOffset = rect.left;
|
|
104
|
+
this.selectionSize = rect.width;
|
|
105
|
+
} else {
|
|
106
|
+
this.selectionOffset = rect.top;
|
|
107
|
+
this.selectionSize = rect.height;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
</script>
|
|
114
|
+
|
|
115
|
+
<style lang="scss">
|
|
116
|
+
@use '../styles/px.scss' as *;
|
|
117
|
+
.px-selection-bar {
|
|
118
|
+
position: relative;
|
|
119
|
+
width: 100%;
|
|
120
|
+
height: 100%;
|
|
121
|
+
|
|
122
|
+
&__selector-container {
|
|
123
|
+
position: absolute;
|
|
124
|
+
@include transition(top left width height);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
&__selector {
|
|
128
|
+
@include abs();
|
|
129
|
+
background-color: currentColor;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
</style>
|