Skip to content

Commit 0062fe1

Browse files
committed
feat: enhance control components with new features and optimizations
- Added new controls (number and boolean dropdown) to TheChild component with watch functionality for value changes. - Updated MultipleControlsDemo to use computed properties for dynamic button labels and icons based on dark mode. - Removed unused computed import in SelectControlDemo for cleaner code. - Enhanced TresLeches component to support slot content and improved height calculations for better resizing behavior. - Refactored useControls to simplify unique key generation for controls. - Expanded LechesSelectOption interface to include an alias property and refined control configurations for better type safety.
1 parent 22e73a4 commit 0062fe1

File tree

7 files changed

+130
-73
lines changed

7 files changed

+130
-73
lines changed

playground/src/pages/basic/parent-child/TheChild.vue

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
22
import type { TresObject } from '@tresjs/core'
33
import { useLoop } from '@tresjs/core'
4-
import { shallowRef } from 'vue'
4+
import { shallowRef, watch } from 'vue'
55
import { OrbitControls } from '@tresjs/cientos'
66
import { useControls } from '@tresjs/leches'
77
@@ -16,8 +16,32 @@ onBeforeRender(({ elapsed }) => {
1616
}
1717
})
1818
19-
const { wireframe } = useControls({
19+
const { wireframe, number, booleanDropdown } = useControls({
2020
wireframe: false,
21+
number: 1,
22+
booleanDropdown: {
23+
value: true,
24+
options: [{
25+
text: 'Option 1',
26+
value: true,
27+
}, {
28+
text: 'Option 2',
29+
value: false,
30+
}, {
31+
text: 'Option 3',
32+
value: true,
33+
}],
34+
},
35+
})
36+
37+
watch(booleanDropdown, (value) => {
38+
// eslint-disable-next-line no-console
39+
console.log('booleanDropdown', value)
40+
})
41+
42+
watch(number, (value) => {
43+
// eslint-disable-next-line no-console
44+
console.log('number', value)
2145
})
2246
</script>
2347

playground/src/pages/controls/MultipleControlsDemo.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { OrbitControls } from '@tresjs/cientos'
66
import { TresLeches, useControls } from '@tresjs/leches'
77
88
/* import '@tresjs/leches/style.css' */
9-
import { ref, watch } from 'vue'
9+
import { computed, ref, watch } from 'vue'
1010
import { useDark, useToggle } from '@vueuse/core'
1111
1212
const cameraRef = ref()
@@ -40,13 +40,13 @@ const { clearColor, wireframe, position, rotation, select } = useControls({
4040
number: 1,
4141
text: 'Hello',
4242
accept: {
43-
label: isDark.value ? 'Light' : 'Dark',
43+
label: computed(() => isDark.value ? 'Light' : 'Dark'),
4444
type: 'button',
4545
variant: 'secondary',
4646
onClick: () => {
4747
toggleDark()
4848
},
49-
icon: isDark.value ? 'i-carbon-sun' : 'i-carbon-moon',
49+
icon: computed(() => isDark.value ? 'i-carbon-sun' : 'i-carbon-moon'),
5050
size: 'block',
5151
},
5252
})

playground/src/pages/controls/SelectControlDemo.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<script setup lang="ts">
2-
import { computed } from 'vue'
32
import { TresCanvas } from '@tresjs/core'
43
import { Environment, OrbitControls } from '@tresjs/cientos'
54
import { TresLeches, useControls } from '@tresjs/leches'

src/components/TresLeches.vue

Lines changed: 72 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ const props = withDefaults(defineProps<{
1919
collapsed: false,
2020
float: true,
2121
})
22+
const slots = defineSlots<{
23+
default: () => any
24+
}>()
25+
2226
const { uuid, collapsed, float } = toRefs(props)
2327
2428
// Panel
@@ -33,28 +37,23 @@ const { width } = useWindowSize()
3337
const { height: windowHeight } = useWindowSize()
3438
const isInitialized = ref(false)
3539
const isResizing = ref(false)
36-
const manualHeight = ref<number | null>(null) // Track manual height override
3740
3841
const DEFAULT_WIDTH = 320
3942
const COLLAPSED_SIZE = 36
40-
const MIN_HEIGHT = 160 // Minimum height for the panel
43+
const MIN_HEIGHT = 148 // Minimum height for the panel
4144
const MAX_HEIGHT = 600 // Maximum height for the panel
4245
const CONTROL_HEIGHT = 44 // Approximate height per control
43-
const FPS_GRAPH_EXTRA_HEIGHT = 20 // Extra padding needed for FPS graph
46+
const FPS_GRAPH_EXTRA_HEIGHT = 26 // Extra padding needed for FPS graph
4447
4548
const panelWidth = ref(DEFAULT_WIDTH)
4649
const resizeEdge = ref<'right' | 'left' | 'bottom' | 'corner' | 'corner-left' | null>(null)
4750
4851
// Controls
4952
const controls = useControlsProvider(uuid?.value)
53+
const hasSlots = ref(false)
5054
5155
defineExpose(controls)
5256
53-
// Add cleanup when component is unmounted
54-
onUnmounted(() => {
55-
dispose(uuid?.value)
56-
})
57-
5857
function onChange(key: string, value: string) {
5958
controls[key].value = value
6059
}
@@ -75,39 +74,30 @@ const groupedControls = computed(() => {
7574
return groups
7675
})
7776
78-
const panelHeight = computed({
79-
get: () => {
80-
if (isCollapsedAndNotFloat.value) { return COLLAPSED_SIZE } // Height when collapsed
81-
82-
// If manually resized, use that height within constraints
83-
if (manualHeight.value !== null) {
84-
const maxAllowedHeight = float.value ? windowHeight.value : MAX_HEIGHT
85-
return Math.min(maxAllowedHeight, Math.max(MIN_HEIGHT, manualHeight.value))
77+
function calculateHeight() {
78+
if (isCollapsedAndNotFloat.value) { return COLLAPSED_SIZE } // Height when collapsed
79+
80+
// Calculate total controls including those in folders
81+
let totalControls = 0
82+
let hasFPSGraph = false
83+
for (const folderName in groupedControls.value) {
84+
const controls = groupedControls.value[folderName]
85+
totalControls += controls.length
86+
// Add height for folder header if it's not the default folder
87+
if (folderName !== 'default') { totalControls += 1 }
88+
// Check if there's an FPS graph control
89+
if (controls.some(control => control.type === 'fpsgraph')) {
90+
hasFPSGraph = true
8691
}
92+
}
8793
88-
// Calculate total controls including those in folders
89-
let totalControls = 0
90-
let hasFPSGraph = false
91-
for (const folderName in groupedControls.value) {
92-
const controls = groupedControls.value[folderName]
93-
totalControls += controls.length
94-
// Add height for folder header if it's not the default folder
95-
if (folderName !== 'default') { totalControls += 1 }
96-
// Check if there's an FPS graph control
97-
if (controls.some(control => control.type === 'fpsgraph')) {
98-
hasFPSGraph = true
99-
}
100-
}
94+
// Calculate height: header (32px) + controls + padding + extra for FPS if present
95+
const calculatedHeight = 32 + (totalControls * CONTROL_HEIGHT) + (hasFPSGraph ? FPS_GRAPH_EXTRA_HEIGHT : 0)
96+
const maxAllowedHeight = float.value ? windowHeight.value : MAX_HEIGHT
97+
return Math.min(maxAllowedHeight, Math.max(MIN_HEIGHT, calculatedHeight))
98+
}
10199
102-
// Calculate height: header (32px) + controls + padding + extra for FPS if present
103-
const calculatedHeight = 32 + (totalControls * CONTROL_HEIGHT) + (hasFPSGraph ? FPS_GRAPH_EXTRA_HEIGHT : 0)
104-
const maxAllowedHeight = float.value ? windowHeight.value : MAX_HEIGHT
105-
return Math.min(maxAllowedHeight, Math.max(MIN_HEIGHT, calculatedHeight))
106-
},
107-
set: (value) => {
108-
manualHeight.value = value
109-
},
110-
})
100+
const panelHeight = ref(calculateHeight())
111101
112102
const paneRef = ref<HTMLElement | null>(null)
113103
const handleRef = ref<HTMLElement | null>(null)
@@ -178,7 +168,7 @@ function startResize(edge: 'right' | 'left' | 'bottom' | 'corner' | 'corner-left
178168
const startX = e.clientX
179169
const startY = e.clientY
180170
const startWidth = panelWidth.value
181-
const startHeight = manualHeight.value ?? panelHeight.value
171+
const startHeight = panelHeight.value
182172
const startDragX = dragPosition.value.x
183173
184174
function onMouseMove(e: MouseEvent) {
@@ -187,22 +177,20 @@ function startResize(edge: 'right' | 'left' | 'bottom' | 'corner' | 'corner-left
187177
if (resizeEdge.value === 'right' || resizeEdge.value === 'corner') {
188178
const deltaX = e.clientX - startX
189179
panelWidth.value = Math.max(280, startWidth + deltaX)
190-
paneRef.value.style.maxWidth = `${panelWidth.value}px`
191180
}
192181
193182
if (resizeEdge.value === 'left' || resizeEdge.value === 'corner-left') {
194183
const deltaX = startX - e.clientX
195184
const newWidth = Math.max(280, startWidth + deltaX)
196185
dragPosition.value.x = startDragX - deltaX
197186
panelWidth.value = newWidth
198-
paneRef.value.style.maxWidth = `${panelWidth.value}px`
199187
}
200188
201189
if (resizeEdge.value === 'bottom' || resizeEdge.value === 'corner' || resizeEdge.value === 'corner-left') {
202190
const deltaY = e.clientY - startY
203191
const maxAllowedHeight = float.value ? windowHeight.value : MAX_HEIGHT
204-
manualHeight.value = Math.min(maxAllowedHeight, Math.max(MIN_HEIGHT, startHeight + deltaY))
205-
paneRef.value.style.maxHeight = `${manualHeight.value}px`
192+
panelHeight.value = Math.min(maxAllowedHeight, Math.max(MIN_HEIGHT, startHeight + deltaY))
193+
206194
// Update gradients after resize
207195
handleScroll()
208196
}
@@ -221,29 +209,31 @@ function startResize(edge: 'right' | 'left' | 'bottom' | 'corner' | 'corner-left
221209
document.addEventListener('mouseup', onMouseUp)
222210
}
223211
212+
watch(panelHeight, (value) => {
213+
if (value && paneRef.value) {
214+
paneRef.value.style.maxHeight = `${value}px`
215+
}
216+
})
217+
218+
watch(panelWidth, (value) => {
219+
if (value && paneRef.value) {
220+
paneRef.value.style.maxWidth = `${value}px`
221+
}
222+
})
223+
224224
function toggleCollapsed() {
225225
if (float.value) {
226226
isCollapsed.value = !isCollapsed.value
227227
}
228228
}
229229
230-
// Initialize panel after slot content is rendered
231-
onMounted(async () => {
232-
handleScroll()
233-
234-
// Wait for slot content to be rendered
235-
await nextTick()
236-
237-
isInitialized.value = true
238-
})
239-
240230
// Update animation when panel state changes
241231
watch(isCollapsed, async (value) => {
242232
if (!value) {
243233
await nextTick() // Wait for slot to be visible
244234
await apply({
245235
width: float.value ? panelWidth.value : 'none',
246-
height: float.value ? (manualHeight.value || panelHeight.value) : 'none',
236+
height: float.value ? panelHeight.value : 'none',
247237
right: float.value ? '1rem' : 'auto',
248238
left: float.value ? 'auto' : '0',
249239
})
@@ -254,11 +244,30 @@ watch(isCollapsed, async (value) => {
254244
255245
function onFolderOpen(value: boolean) {
256246
panelHeight.value = panelHeight.value + (44 * (value ? 1 : -1))
257-
if (manualHeight.value && paneRef.value) {
258-
manualHeight.value = manualHeight.value + (44 * (value ? 1 : -1))
259-
paneRef.value.style.maxHeight = `${manualHeight.value}px`
260-
}
261247
}
248+
249+
const slotsRef = ref()
250+
251+
watch(slotsRef, (value) => {
252+
panelHeight.value = panelHeight.value + value.clientHeight
253+
})
254+
255+
// Initialize panel after slot content is rendered
256+
onMounted(async () => {
257+
handleScroll()
258+
259+
// Wait for slot content to be rendered
260+
await nextTick()
261+
262+
isInitialized.value = true
263+
264+
hasSlots.value = slots?.default ? slots?.default().length > 0 : false
265+
})
266+
267+
// Add cleanup when component is unmounted
268+
onUnmounted(() => {
269+
dispose(uuid?.value)
270+
})
262271
</script>
263272

264273
<template>
@@ -303,7 +312,7 @@ function onFolderOpen(value: boolean) {
303312
</button>
304313
</div>
305314
</header>
306-
<div v-show="!isCollapsed" class="tl-flex-1 tl-relative tl-overflow-hidden tl-my-4">
315+
<div v-show="!isCollapsed" class="tl-flex-1 tl-relative tl-overflow-hidden tl-py-4">
307316
<!-- Gradient overlays moved outside scrollable area -->
308317
<div
309318
class="tl-pointer-events-none tl-absolute tl-left-0 tl-right-0 tl-top-0 tl-h-8 tl-bg-gradient-linear tl-bg-gradient-to-b tl-from-white dark:tl-from-dark-200 tl-to-transparent tl-z-20 tl-opacity-0 tl-transition-opacity duration-200"
@@ -337,7 +346,10 @@ function onFolderOpen(value: boolean) {
337346
/>
338347
</template>
339348
</template>
340-
<slot></slot>
349+
350+
<div v-if="hasSlots" ref="slotsRef" class="tl-px-4">
351+
<slot></slot>
352+
</div>
341353
</div>
342354
</div>
343355
<!-- Resize handles -->

src/composables/useControls.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,8 @@ export const useControls = (
116116
// If the control is part of a folder, prefix the key with the folder name
117117
if (folderName) {
118118
key = `${folderName.replace(/[\u{1F300}-\u{1F9FF}]/gu, '').trim()}${capitalize(key)}`
119-
uniqueKey = `${uuid}-${key}`
120-
}
121-
else {
122-
uniqueKey = `${uuid}-${key}`
123119
}
120+
uniqueKey = `${uuid}-${key}`
124121

125122
// If the value is an object with control options
126123
if (typeof value === 'object' && !isRef(value) && !Array.isArray(value) && value.value !== undefined) {

src/types/index.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,34 @@
11
export interface LechesSelectOption {
22
text: string
3+
alias: string
34
value: string | number
45
}
56

6-
export interface LechesControl<T = any> {
7+
export type LechesValue = string | number | boolean
8+
9+
export type LechesCotrolConfigTypes = 'select' | 'button' | 'range' | 'boolean' | 'text' | 'number'
10+
export interface LechesBaseControlConfig {
11+
value: unknown
12+
label?: string
13+
icon?: string
14+
type?: LechesCotrolConfigTypes
15+
}
16+
17+
export interface LechesSelectControlConfig extends LechesBaseControlConfig {
18+
options: string[] | LechesSelectOption[]
19+
}
20+
21+
export interface LechesSelectControlButton extends LechesBaseControlConfig {
22+
variant: 'primary' | 'secondary',
23+
onClick?: () => void
24+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'block'
25+
}
26+
27+
export interface LechesControlsConfig {
28+
[key:string]:
29+
}
30+
31+
export interface LechesControl<T = unknown> {
732
key: string
833
label: string
934
name: string
@@ -17,5 +42,5 @@ export interface LechesControl<T = any> {
1742
min?: number
1843
max?: number
1944
step?: number
20-
onUpdate?: (values: any[]) => void
45+
onUpdate?: (values: unknown[]) => void
2146
}

stats.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)