vue-pane 0.0.0
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/LICENSE +21 -0
- package/README.md +424 -0
- package/dist/src/components/PButton.vue.d.ts +11 -0
- package/dist/src/components/PCheckbox.vue.d.ts +15 -0
- package/dist/src/components/PColor.vue.d.ts +16 -0
- package/dist/src/components/PFolder.vue.d.ts +27 -0
- package/dist/src/components/PGraph.vue.d.ts +10 -0
- package/dist/src/components/PLabel.vue.d.ts +17 -0
- package/dist/src/components/PMonitor.vue.d.ts +8 -0
- package/dist/src/components/PMonitorMulti.vue.d.ts +8 -0
- package/dist/src/components/PNumber.vue.d.ts +18 -0
- package/dist/src/components/PPoint2d.vue.d.ts +26 -0
- package/dist/src/components/PSelect.vue.d.ts +19 -0
- package/dist/src/components/PSeparator.vue.d.ts +3 -0
- package/dist/src/components/PSlider.vue.d.ts +18 -0
- package/dist/src/components/PTab.vue.d.ts +25 -0
- package/dist/src/components/PText.vue.d.ts +15 -0
- package/dist/src/components/PTooltipIcon.vue.d.ts +3 -0
- package/dist/src/components/VPane.vue.d.ts +28 -0
- package/dist/src/composables/useFoldable.d.ts +5 -0
- package/dist/src/composables/usePaneConfig.d.ts +8 -0
- package/dist/src/composables/usePickerFold.d.ts +2 -0
- package/dist/src/composables/useTooltip.d.ts +19 -0
- package/dist/src/index.d.ts +19 -0
- package/dist/vue-pane.css +2 -0
- package/dist/vue-pane.js +3076 -0
- package/dist/vue-pane.umd.cjs +1 -0
- package/package.json +79 -0
- package/src/components/PButton.vue +53 -0
- package/src/components/PCheckbox.vue +37 -0
- package/src/components/PColor.vue +107 -0
- package/src/components/PConfig.vue +10 -0
- package/src/components/PFolder.vue +81 -0
- package/src/components/PGraph.vue +49 -0
- package/src/components/PLabel.vue +50 -0
- package/src/components/PMonitor.vue +28 -0
- package/src/components/PMonitorMulti.vue +30 -0
- package/src/components/PNumber.vue +162 -0
- package/src/components/PPoint2d.vue +191 -0
- package/src/components/PSelect.vue +44 -0
- package/src/components/PSeparator.vue +8 -0
- package/src/components/PSlider.vue +96 -0
- package/src/components/PTab.vue +73 -0
- package/src/components/PText.vue +30 -0
- package/src/components/PTooltipIcon.vue +30 -0
- package/src/components/VPane.vue +61 -0
- package/src/composables/useFoldable.ts +128 -0
- package/src/composables/usePaneConfig.ts +25 -0
- package/src/composables/usePickerFold.ts +46 -0
- package/src/composables/useTooltip.ts +27 -0
- package/src/index.ts +38 -0
- package/src/styles/_vp.scss +12 -0
- package/src/styles/common/_defs.scss +56 -0
- package/src/styles/index.scss +1 -0
- package/src/styles/view/_button.scss +12 -0
- package/src/styles/view/_checkbox.scss +54 -0
- package/src/styles/view/_color.scss +57 -0
- package/src/styles/view/_folder.scss +70 -0
- package/src/styles/view/_graph.scss +11 -0
- package/src/styles/view/_label.scss +37 -0
- package/src/styles/view/_list.scss +17 -0
- package/src/styles/view/_log.scss +13 -0
- package/src/styles/view/_monitor-multi.scss +18 -0
- package/src/styles/view/_number.scss +110 -0
- package/src/styles/view/_point-2d.scss +61 -0
- package/src/styles/view/_root.scss +126 -0
- package/src/styles/view/_separator.scss +15 -0
- package/src/styles/view/_slider.scss +32 -0
- package/src/styles/view/_tab.scss +139 -0
- package/src/styles/view/_text.scss +24 -0
- package/src/styles/view/_tooltip.scss +17 -0
- package/src/styles/view/_views.scss +2 -0
- package/src/styles/view/placeholder/_button.scss +28 -0
- package/src/styles/view/placeholder/_container.scss +80 -0
- package/src/styles/view/placeholder/_folder.scss +102 -0
- package/src/styles/view/placeholder/_input.scss +26 -0
- package/src/styles/view/placeholder/_list.scss +35 -0
- package/src/styles/view/placeholder/_monitor.scss +26 -0
- package/src/styles/view/placeholder/_texts.scss +11 -0
- package/src/styles/view/placeholder/_theme.scss +111 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import PLabel from './PLabel.vue'
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
value,
|
|
6
|
+
label,
|
|
7
|
+
tooltip,
|
|
8
|
+
} = defineProps<{
|
|
9
|
+
value: unknown
|
|
10
|
+
label?: string
|
|
11
|
+
tooltip?: string
|
|
12
|
+
}>()
|
|
13
|
+
</script>
|
|
14
|
+
<template>
|
|
15
|
+
<PLabel
|
|
16
|
+
:label="label"
|
|
17
|
+
:tooltip="tooltip"
|
|
18
|
+
>
|
|
19
|
+
<div class="vp-monitor">
|
|
20
|
+
<div class="vp-monitor__value">
|
|
21
|
+
{{ value }}
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</PLabel>
|
|
25
|
+
</template>
|
|
26
|
+
<style lang="scss">
|
|
27
|
+
@use '../styles/view/log';
|
|
28
|
+
</style>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import PLabel from './PLabel.vue'
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
value,
|
|
6
|
+
label,
|
|
7
|
+
tooltip,
|
|
8
|
+
} = defineProps<{
|
|
9
|
+
value: unknown
|
|
10
|
+
label?: string
|
|
11
|
+
tooltip?: string
|
|
12
|
+
}>()
|
|
13
|
+
</script>
|
|
14
|
+
<template>
|
|
15
|
+
<PLabel
|
|
16
|
+
:label="label"
|
|
17
|
+
:tooltip="tooltip"
|
|
18
|
+
>
|
|
19
|
+
<div class="vp-monitor-multi">
|
|
20
|
+
<textarea
|
|
21
|
+
class="vp-monitor-multi__value"
|
|
22
|
+
readonly
|
|
23
|
+
:value="String(value)"
|
|
24
|
+
/>
|
|
25
|
+
</div>
|
|
26
|
+
</PLabel>
|
|
27
|
+
</template>
|
|
28
|
+
<style lang="scss">
|
|
29
|
+
@use '../styles/view/monitor-multi';
|
|
30
|
+
</style>
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref, watch } from 'vue'
|
|
3
|
+
import PLabel from './PLabel.vue'
|
|
4
|
+
|
|
5
|
+
const model = defineModel<number>({ required: true })
|
|
6
|
+
const {
|
|
7
|
+
label,
|
|
8
|
+
tooltip,
|
|
9
|
+
min,
|
|
10
|
+
max,
|
|
11
|
+
step = 1,
|
|
12
|
+
} = defineProps<{
|
|
13
|
+
label?: string
|
|
14
|
+
tooltip?: string
|
|
15
|
+
min?: number
|
|
16
|
+
max?: number
|
|
17
|
+
step?: number
|
|
18
|
+
}>()
|
|
19
|
+
|
|
20
|
+
const isDragging = ref(false)
|
|
21
|
+
const isFocused = ref(false)
|
|
22
|
+
const localValue = ref(String(model.value))
|
|
23
|
+
const dragPixelDelta = ref(0)
|
|
24
|
+
|
|
25
|
+
watch(model, (val) => {
|
|
26
|
+
if (!isFocused.value) {
|
|
27
|
+
localValue.value = String(val)
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
function clamp(val: number): number {
|
|
32
|
+
if (min !== undefined) val = Math.max(min, val)
|
|
33
|
+
if (max !== undefined) val = Math.min(max, val)
|
|
34
|
+
return val
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function snapToStep(val: number): number {
|
|
38
|
+
return Math.round(val / step) * step
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const guideBodyPath = computed(() => {
|
|
42
|
+
const x = dragPixelDelta.value
|
|
43
|
+
return `M 0,4 L${x},4`
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const guideHeadPath = computed(() => {
|
|
47
|
+
const x = dragPixelDelta.value
|
|
48
|
+
if (x === 0) return ''
|
|
49
|
+
const aox = x + (x > 0 ? -1 : 1)
|
|
50
|
+
const adx = Math.max(-4, Math.min(4, -aox))
|
|
51
|
+
return `M ${aox + adx},0 L${aox},4 L${aox + adx},8 M ${x},-1 L${x},9`
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const tooltipText = computed(() => {
|
|
55
|
+
const decimals = step < 1 ? (String(step).split('.')[1]?.length ?? 0) : 0
|
|
56
|
+
return model.value.toFixed(decimals)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
function onFocus() {
|
|
60
|
+
isFocused.value = true
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function onBlur() {
|
|
64
|
+
isFocused.value = false
|
|
65
|
+
const parsed = parseFloat(localValue.value)
|
|
66
|
+
if (!isNaN(parsed)) {
|
|
67
|
+
model.value = clamp(parsed)
|
|
68
|
+
} else {
|
|
69
|
+
localValue.value = String(model.value)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function onKeydown(e: KeyboardEvent) {
|
|
74
|
+
if (e.key === 'Enter') {
|
|
75
|
+
(e.target as HTMLInputElement).blur()
|
|
76
|
+
} else if (e.key === 'ArrowUp') {
|
|
77
|
+
e.preventDefault()
|
|
78
|
+
const multiplier = e.shiftKey ? 10 : 1
|
|
79
|
+
model.value = clamp(model.value + step * multiplier)
|
|
80
|
+
} else if (e.key === 'ArrowDown') {
|
|
81
|
+
e.preventDefault()
|
|
82
|
+
const multiplier = e.shiftKey ? 10 : 1
|
|
83
|
+
model.value = clamp(model.value - step * multiplier)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let dragStartX = 0
|
|
88
|
+
let dragStartValue = 0
|
|
89
|
+
|
|
90
|
+
function onKnobPointerDown(e: PointerEvent) {
|
|
91
|
+
e.preventDefault()
|
|
92
|
+
;(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId)
|
|
93
|
+
isDragging.value = true
|
|
94
|
+
dragStartX = e.clientX
|
|
95
|
+
dragStartValue = model.value
|
|
96
|
+
dragPixelDelta.value = 0
|
|
97
|
+
document.body.style.cursor = 'ew-resize'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function onKnobPointerMove(e: PointerEvent) {
|
|
101
|
+
if (!isDragging.value) return
|
|
102
|
+
const delta = e.clientX - dragStartX
|
|
103
|
+
dragPixelDelta.value = delta
|
|
104
|
+
model.value = clamp(snapToStep(dragStartValue + delta))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function onKnobPointerUp() {
|
|
108
|
+
isDragging.value = false
|
|
109
|
+
dragPixelDelta.value = 0
|
|
110
|
+
document.body.style.cursor = ''
|
|
111
|
+
}
|
|
112
|
+
</script>
|
|
113
|
+
|
|
114
|
+
<template>
|
|
115
|
+
<PLabel
|
|
116
|
+
:label="label"
|
|
117
|
+
:tooltip="tooltip"
|
|
118
|
+
>
|
|
119
|
+
<div
|
|
120
|
+
class="vp-text vp-text--number"
|
|
121
|
+
:class="{ 'vp-text--dragging': isDragging }"
|
|
122
|
+
>
|
|
123
|
+
<input
|
|
124
|
+
v-model="localValue"
|
|
125
|
+
class="vp-text__input"
|
|
126
|
+
type="text"
|
|
127
|
+
@focus="onFocus"
|
|
128
|
+
@blur="onBlur"
|
|
129
|
+
@keydown="onKeydown"
|
|
130
|
+
>
|
|
131
|
+
<div
|
|
132
|
+
class="vp-text__knob"
|
|
133
|
+
@pointerdown="onKnobPointerDown"
|
|
134
|
+
@pointermove="onKnobPointerMove"
|
|
135
|
+
@pointerup="onKnobPointerUp"
|
|
136
|
+
@pointercancel="onKnobPointerUp"
|
|
137
|
+
>
|
|
138
|
+
<svg class="vp-text__guide">
|
|
139
|
+
<path
|
|
140
|
+
class="vp-text__guide-body"
|
|
141
|
+
:d="guideBodyPath"
|
|
142
|
+
/>
|
|
143
|
+
<path
|
|
144
|
+
class="vp-text__guide-head"
|
|
145
|
+
:d="guideHeadPath"
|
|
146
|
+
/>
|
|
147
|
+
</svg>
|
|
148
|
+
<div
|
|
149
|
+
class="vp-text__drag-tooltip"
|
|
150
|
+
:style="{ left: `${dragPixelDelta}px` }"
|
|
151
|
+
>
|
|
152
|
+
{{ tooltipText }}
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
</PLabel>
|
|
157
|
+
</template>
|
|
158
|
+
|
|
159
|
+
<style lang="scss">
|
|
160
|
+
@use '../styles/view/text';
|
|
161
|
+
@use '../styles/view/number';
|
|
162
|
+
</style>
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onMounted, ref } from 'vue'
|
|
3
|
+
import { usePickerFold } from '../composables/usePickerFold'
|
|
4
|
+
import PLabel from './PLabel.vue'
|
|
5
|
+
|
|
6
|
+
const model = defineModel<{ x: number, y: number }>({ required: true })
|
|
7
|
+
const {
|
|
8
|
+
label,
|
|
9
|
+
tooltip,
|
|
10
|
+
min = -1,
|
|
11
|
+
max = 1,
|
|
12
|
+
} = defineProps<{
|
|
13
|
+
label?: string
|
|
14
|
+
tooltip?: string
|
|
15
|
+
min?: number
|
|
16
|
+
max?: number
|
|
17
|
+
}>()
|
|
18
|
+
|
|
19
|
+
const isOpen = ref(false)
|
|
20
|
+
const panelRef = ref<HTMLElement | null>(null)
|
|
21
|
+
const canvasRef = ref<HTMLCanvasElement | null>(null)
|
|
22
|
+
|
|
23
|
+
const isComplete = usePickerFold(panelRef, isOpen)
|
|
24
|
+
const canvasSize = 160
|
|
25
|
+
|
|
26
|
+
const localX = computed(() => model.value.x.toFixed(3))
|
|
27
|
+
const localY = computed(() => model.value.y.toFixed(3))
|
|
28
|
+
|
|
29
|
+
function clamp(val: number): number {
|
|
30
|
+
return Math.max(min, Math.min(max, val))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function onXBlur(e: Event) {
|
|
34
|
+
const val = parseFloat((e.target as HTMLInputElement).value)
|
|
35
|
+
if (!isNaN(val)) model.value = { x: clamp(val), y: model.value.y }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function onYBlur(e: Event) {
|
|
39
|
+
const val = parseFloat((e.target as HTMLInputElement).value)
|
|
40
|
+
if (!isNaN(val)) model.value = { x: model.value.x, y: clamp(val) }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function toggle() {
|
|
44
|
+
isOpen.value = !isOpen.value
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function drawCanvas() {
|
|
48
|
+
const canvas = canvasRef.value
|
|
49
|
+
if (!canvas) return
|
|
50
|
+
const ctx = canvas.getContext('2d')!
|
|
51
|
+
const size = canvasSize
|
|
52
|
+
ctx.clearRect(0, 0, size, size)
|
|
53
|
+
ctx.fillStyle = 'rgba(0,0,0,0.3)'
|
|
54
|
+
ctx.fillRect(0, 0, size, size)
|
|
55
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.1)'
|
|
56
|
+
ctx.lineWidth = 1
|
|
57
|
+
ctx.beginPath()
|
|
58
|
+
ctx.moveTo(size / 2, 0)
|
|
59
|
+
ctx.lineTo(size / 2, size)
|
|
60
|
+
ctx.moveTo(0, size / 2)
|
|
61
|
+
ctx.lineTo(size, size / 2)
|
|
62
|
+
ctx.stroke()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
onMounted(() => {
|
|
66
|
+
drawCanvas()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const crosshairX = computed(() => {
|
|
70
|
+
const range = max - min
|
|
71
|
+
return ((model.value.x - min) / range) * canvasSize
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const crosshairY = computed(() => {
|
|
75
|
+
const range = max - min
|
|
76
|
+
return canvasSize - ((model.value.y - min) / range) * canvasSize
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
let canvasDragging = false
|
|
80
|
+
|
|
81
|
+
function posToValue(e: PointerEvent) {
|
|
82
|
+
const canvas = canvasRef.value!
|
|
83
|
+
const rect = canvas.getBoundingClientRect()
|
|
84
|
+
const rx = (e.clientX - rect.left) / rect.width
|
|
85
|
+
const ry = 1 - (e.clientY - rect.top) / rect.height
|
|
86
|
+
const range = max - min
|
|
87
|
+
return {
|
|
88
|
+
x: clamp(min + rx * range),
|
|
89
|
+
y: clamp(min + ry * range),
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function onCanvasDown(e: PointerEvent) {
|
|
94
|
+
e.preventDefault()
|
|
95
|
+
;(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId)
|
|
96
|
+
canvasDragging = true
|
|
97
|
+
model.value = posToValue(e)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function onCanvasMove(e: PointerEvent) {
|
|
101
|
+
if (!canvasDragging) return
|
|
102
|
+
model.value = posToValue(e)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function onCanvasUp() {
|
|
106
|
+
canvasDragging = false
|
|
107
|
+
}
|
|
108
|
+
</script>
|
|
109
|
+
<template>
|
|
110
|
+
<PLabel
|
|
111
|
+
:label="label"
|
|
112
|
+
:tooltip="tooltip"
|
|
113
|
+
>
|
|
114
|
+
<div
|
|
115
|
+
class="vp-point-2d"
|
|
116
|
+
:class="{ 'vp-point-2d--expanded': isOpen, 'vp-point-2d--complete': isComplete }"
|
|
117
|
+
>
|
|
118
|
+
<div class="vp-point-2d__header">
|
|
119
|
+
<button
|
|
120
|
+
class="vp-point-2d__btn"
|
|
121
|
+
@click="toggle"
|
|
122
|
+
>
|
|
123
|
+
<svg viewBox="0 0 16 16">
|
|
124
|
+
<path d="M8 2 L8 14 M2 8 L14 8" />
|
|
125
|
+
<circle
|
|
126
|
+
cx="8"
|
|
127
|
+
cy="8"
|
|
128
|
+
r="1.5"
|
|
129
|
+
/>
|
|
130
|
+
</svg>
|
|
131
|
+
</button>
|
|
132
|
+
<div class="vp-point-2d__inputs">
|
|
133
|
+
<div style="display:flex; gap:2px">
|
|
134
|
+
<div
|
|
135
|
+
class="vp-text vp-text--number vp-text--first"
|
|
136
|
+
style="flex:1"
|
|
137
|
+
>
|
|
138
|
+
<input
|
|
139
|
+
class="vp-text__input"
|
|
140
|
+
type="text"
|
|
141
|
+
:value="localX"
|
|
142
|
+
@blur="onXBlur"
|
|
143
|
+
@keydown.enter="(e) => (e.target as HTMLElement).blur()"
|
|
144
|
+
>
|
|
145
|
+
</div>
|
|
146
|
+
<div
|
|
147
|
+
class="vp-text vp-text--number vp-text--last"
|
|
148
|
+
style="flex:1"
|
|
149
|
+
>
|
|
150
|
+
<input
|
|
151
|
+
class="vp-text__input"
|
|
152
|
+
type="text"
|
|
153
|
+
:value="localY"
|
|
154
|
+
@blur="onYBlur"
|
|
155
|
+
@keydown.enter="(e) => (e.target as HTMLElement).blur()"
|
|
156
|
+
>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
<div
|
|
162
|
+
ref="panelRef"
|
|
163
|
+
class="vp-point-2d__picker-panel"
|
|
164
|
+
>
|
|
165
|
+
<div class="vp-point-2d__picker-wrap">
|
|
166
|
+
<canvas
|
|
167
|
+
ref="canvasRef"
|
|
168
|
+
class="vp-point-2d__canvas"
|
|
169
|
+
:height="canvasSize"
|
|
170
|
+
:width="canvasSize"
|
|
171
|
+
@pointerdown="onCanvasDown"
|
|
172
|
+
@pointermove="onCanvasMove"
|
|
173
|
+
@pointerup="onCanvasUp"
|
|
174
|
+
@pointercancel="onCanvasUp"
|
|
175
|
+
/>
|
|
176
|
+
<div
|
|
177
|
+
class="vp-point-2d__crosshair-h"
|
|
178
|
+
:style="{ top: crosshairY + 'px' }"
|
|
179
|
+
/>
|
|
180
|
+
<div
|
|
181
|
+
class="vp-point-2d__crosshair-v"
|
|
182
|
+
:style="{ left: crosshairX + 'px' }"
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</PLabel>
|
|
188
|
+
</template>
|
|
189
|
+
<style lang="scss">
|
|
190
|
+
@use '../styles/view/point-2d';
|
|
191
|
+
</style>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import PLabel from './PLabel.vue'
|
|
3
|
+
|
|
4
|
+
const model = defineModel<string | number>({ required: true })
|
|
5
|
+
const {
|
|
6
|
+
label,
|
|
7
|
+
tooltip,
|
|
8
|
+
options,
|
|
9
|
+
} = defineProps<{
|
|
10
|
+
label?: string
|
|
11
|
+
tooltip?: string
|
|
12
|
+
options: { value: string | number, label: string }[]
|
|
13
|
+
}>()
|
|
14
|
+
</script>
|
|
15
|
+
<template>
|
|
16
|
+
<PLabel
|
|
17
|
+
:label="label"
|
|
18
|
+
:tooltip="tooltip"
|
|
19
|
+
>
|
|
20
|
+
<div class="vp-select">
|
|
21
|
+
<select
|
|
22
|
+
class="vp-select__input"
|
|
23
|
+
:value="model"
|
|
24
|
+
@change="model = ($event.target as HTMLSelectElement).value"
|
|
25
|
+
>
|
|
26
|
+
<option
|
|
27
|
+
v-for="opt in options"
|
|
28
|
+
:key="opt.value"
|
|
29
|
+
:value="opt.value"
|
|
30
|
+
>
|
|
31
|
+
{{ opt.label }}
|
|
32
|
+
</option>
|
|
33
|
+
</select>
|
|
34
|
+
<div class="vp-select__arrow">
|
|
35
|
+
<svg viewBox="0 0 16 16">
|
|
36
|
+
<path d="M 2 5 L 8 11 L 14 5" />
|
|
37
|
+
</svg>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</PLabel>
|
|
41
|
+
</template>
|
|
42
|
+
<style lang="scss">
|
|
43
|
+
@use '../styles/view/list';
|
|
44
|
+
</style>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
import PLabel from './PLabel.vue'
|
|
4
|
+
|
|
5
|
+
const model = defineModel<number>({ required: true })
|
|
6
|
+
const {
|
|
7
|
+
label,
|
|
8
|
+
tooltip,
|
|
9
|
+
min = 0,
|
|
10
|
+
max = 1,
|
|
11
|
+
step = null,
|
|
12
|
+
} = defineProps<{
|
|
13
|
+
label?: string
|
|
14
|
+
tooltip?: string
|
|
15
|
+
min?: number
|
|
16
|
+
max?: number
|
|
17
|
+
step?: number | null
|
|
18
|
+
}>()
|
|
19
|
+
|
|
20
|
+
const trackRef = ref<HTMLElement | null>(null)
|
|
21
|
+
const isDragging = ref(false)
|
|
22
|
+
|
|
23
|
+
const knobPercent = computed(() => {
|
|
24
|
+
const range = max - min
|
|
25
|
+
if (range === 0) return 0
|
|
26
|
+
return ((model.value - min) / range) * 100
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
function clamp(val: number): number {
|
|
30
|
+
return Math.max(min, Math.min(max, val))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function valueFromPointer(e: PointerEvent): number {
|
|
34
|
+
const el = trackRef.value
|
|
35
|
+
if (!el) return model.value
|
|
36
|
+
const rect = el.getBoundingClientRect()
|
|
37
|
+
const ratio = (e.clientX - rect.left) / rect.width
|
|
38
|
+
const raw = min + ratio * (max - min)
|
|
39
|
+
if (step === null) return clamp(raw)
|
|
40
|
+
return clamp(Math.round(raw / step) * step)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function onTrackPointerDown(e: PointerEvent) {
|
|
44
|
+
e.preventDefault()
|
|
45
|
+
;(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId)
|
|
46
|
+
isDragging.value = true
|
|
47
|
+
model.value = valueFromPointer(e)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function onTrackPointerMove(e: PointerEvent) {
|
|
51
|
+
if (!isDragging.value) return
|
|
52
|
+
model.value = valueFromPointer(e)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function onPointerUp() {
|
|
56
|
+
isDragging.value = false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function onKeydown(e: KeyboardEvent) {
|
|
60
|
+
const increment = step ?? (max - min) / 100
|
|
61
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
|
|
62
|
+
e.preventDefault()
|
|
63
|
+
model.value = clamp(model.value + increment * (e.shiftKey ? 10 : 1))
|
|
64
|
+
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
|
|
65
|
+
e.preventDefault()
|
|
66
|
+
model.value = clamp(model.value - increment * (e.shiftKey ? 10 : 1))
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
</script>
|
|
70
|
+
<template>
|
|
71
|
+
<PLabel
|
|
72
|
+
:label="label"
|
|
73
|
+
:tooltip="tooltip"
|
|
74
|
+
>
|
|
75
|
+
<div class="vp-slider">
|
|
76
|
+
<div
|
|
77
|
+
ref="trackRef"
|
|
78
|
+
class="vp-slider__track"
|
|
79
|
+
tabindex="0"
|
|
80
|
+
@pointerdown="onTrackPointerDown"
|
|
81
|
+
@pointermove="onTrackPointerMove"
|
|
82
|
+
@pointerup="onPointerUp"
|
|
83
|
+
@pointercancel="onPointerUp"
|
|
84
|
+
@keydown="onKeydown"
|
|
85
|
+
>
|
|
86
|
+
<div
|
|
87
|
+
class="vp-slider__knob"
|
|
88
|
+
:style="{ left: knobPercent + '%' }"
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</PLabel>
|
|
93
|
+
</template>
|
|
94
|
+
<style lang="scss">
|
|
95
|
+
@use '../styles/view/slider';
|
|
96
|
+
</style>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { usePaneConfig } from '../composables/usePaneConfig'
|
|
3
|
+
import { useTooltip } from '../composables/useTooltip'
|
|
4
|
+
|
|
5
|
+
const model = defineModel<number>({ default: 0 })
|
|
6
|
+
const {
|
|
7
|
+
tabs,
|
|
8
|
+
tooltips,
|
|
9
|
+
} = defineProps<{
|
|
10
|
+
tabs: string[]
|
|
11
|
+
tooltips?: string[]
|
|
12
|
+
}>()
|
|
13
|
+
|
|
14
|
+
const { floatingStyles, visible, activeText, show, hide } = useTooltip()
|
|
15
|
+
const config = usePaneConfig()
|
|
16
|
+
|
|
17
|
+
function setTab(i: number) {
|
|
18
|
+
model.value = i
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
<template>
|
|
22
|
+
<div class="vp-tab">
|
|
23
|
+
<div class="vp-tab__title-bar">
|
|
24
|
+
<div
|
|
25
|
+
v-for="(tab, i) in tabs"
|
|
26
|
+
:key="i"
|
|
27
|
+
class="vp-tab-item"
|
|
28
|
+
:class="{ 'vp-tab-item--selected': i === model }"
|
|
29
|
+
>
|
|
30
|
+
<button
|
|
31
|
+
class="vp-tab-item__btn"
|
|
32
|
+
@click="setTab(i)"
|
|
33
|
+
>
|
|
34
|
+
<span class="vp-tab-item__label">
|
|
35
|
+
{{ tab }}
|
|
36
|
+
<component
|
|
37
|
+
:is="config.tooltipIcon"
|
|
38
|
+
v-if="tooltips?.[i] && config.tooltipIcon"
|
|
39
|
+
@mouseenter="tooltips?.[i] && show($event, tooltips[i])"
|
|
40
|
+
@mouseleave="hide"
|
|
41
|
+
/>
|
|
42
|
+
</span>
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
<Teleport to="body">
|
|
47
|
+
<div
|
|
48
|
+
v-if="visible"
|
|
49
|
+
ref="floatingEl"
|
|
50
|
+
class="vp-tooltip"
|
|
51
|
+
:style="floatingStyles"
|
|
52
|
+
>
|
|
53
|
+
{{ activeText }}
|
|
54
|
+
</div>
|
|
55
|
+
</Teleport>
|
|
56
|
+
<div class="vp-tab__indent" />
|
|
57
|
+
<div class="vp-tab__content">
|
|
58
|
+
<div
|
|
59
|
+
v-for="(tab, i) in tabs"
|
|
60
|
+
v-show="i === model"
|
|
61
|
+
:key="i"
|
|
62
|
+
class="vp-tab-page"
|
|
63
|
+
>
|
|
64
|
+
<div class="vp-tab-page__content">
|
|
65
|
+
<slot :name="tab" />
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</template>
|
|
71
|
+
<style lang="scss">
|
|
72
|
+
@use '../styles/view/tab';
|
|
73
|
+
</style>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import PLabel from './PLabel.vue'
|
|
3
|
+
|
|
4
|
+
const model = defineModel<string>({ required: true })
|
|
5
|
+
const {
|
|
6
|
+
label,
|
|
7
|
+
tooltip,
|
|
8
|
+
} = defineProps<{
|
|
9
|
+
label?: string
|
|
10
|
+
tooltip?: string
|
|
11
|
+
}>()
|
|
12
|
+
</script>
|
|
13
|
+
<template>
|
|
14
|
+
<PLabel
|
|
15
|
+
:label="label"
|
|
16
|
+
:tooltip="tooltip"
|
|
17
|
+
>
|
|
18
|
+
<div class="vp-text">
|
|
19
|
+
<input
|
|
20
|
+
class="vp-text__input"
|
|
21
|
+
type="text"
|
|
22
|
+
:value="model"
|
|
23
|
+
@input="model = ($event.target as HTMLInputElement).value"
|
|
24
|
+
>
|
|
25
|
+
</div>
|
|
26
|
+
</PLabel>
|
|
27
|
+
</template>
|
|
28
|
+
<style lang="scss">
|
|
29
|
+
@use '../styles/view/text';
|
|
30
|
+
</style>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<svg
|
|
3
|
+
viewBox="0 0 16 16"
|
|
4
|
+
width="10"
|
|
5
|
+
height="10"
|
|
6
|
+
fill="none"
|
|
7
|
+
>
|
|
8
|
+
<circle
|
|
9
|
+
cx="8"
|
|
10
|
+
cy="8"
|
|
11
|
+
r="7"
|
|
12
|
+
stroke="currentColor"
|
|
13
|
+
stroke-width="1.5"
|
|
14
|
+
/>
|
|
15
|
+
<circle
|
|
16
|
+
cx="8"
|
|
17
|
+
cy="5.5"
|
|
18
|
+
r="1"
|
|
19
|
+
fill="currentColor"
|
|
20
|
+
/>
|
|
21
|
+
<rect
|
|
22
|
+
x="7"
|
|
23
|
+
y="7.5"
|
|
24
|
+
width="2"
|
|
25
|
+
height="4"
|
|
26
|
+
rx="1"
|
|
27
|
+
fill="currentColor"
|
|
28
|
+
/>
|
|
29
|
+
</svg>
|
|
30
|
+
</template>
|