diff --git a/package.json b/package.json index b09b768..4172f8e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "animatr", "private": true, - "version": "0.8.3", + "version": "0.8.4", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.vue b/src/App.vue index e594168..bb2f7fb 100644 --- a/src/App.vue +++ b/src/App.vue @@ -812,7 +812,11 @@ onBeforeUnmount(() => { right: 0; height: 14px; cursor: ns-resize; - z-index: 20; + /* Must sit above everything inside the Timeline — its .scrub-bar is + `position: sticky; z-index: 40`, which (because the Timeline root + has no z-index of its own) competes in this same stacking context + and would otherwise swallow the bottom half of the grab area. */ + z-index: 100; touch-action: none; background: transparent; transition: background 120ms var(--ease); diff --git a/src/components/CanvasStage.vue b/src/components/CanvasStage.vue index a175bfd..3cb9760 100644 --- a/src/components/CanvasStage.vue +++ b/src/components/CanvasStage.vue @@ -492,12 +492,11 @@ function buttonChildConfig>(cfg: T): T { return { ...cfg, draggable: false, listening: false } } -function buttonStackForState( +function buttonStackForTime( asset: ButtonAsset, - state: ButtonState, + stateTime: number, ): Array<{ layer: Layer; asset: Asset; props: AnimatableProps }> { const out: Array<{ layer: Layer; asset: Asset; props: AnimatableProps }> = [] - const stateTime = BUTTON_STATE_TIMES[state] const layers = asset.layers.slice().reverse() for (const l of layers) { if (!l.visible) continue @@ -508,13 +507,24 @@ function buttonStackForState( return out } +function setStageCursor(cursor: string) { + const el = stageRef.value?.getStage().container() + if (el) el.style.cursor = cursor +} + function onButtonEnter(layer: Layer) { + // Pointer cursor whenever a button asset is under the mouse — it's + // interactive UX even in edit mode, where it's draggable like any + // other layer. In preview mode, also flip the state machine into + // `hover` so visual state changes fire. + if (!isLayerEffectivelyLocked(layer)) setStageCursor('pointer') if (!stagePreview.value) return const cur = buttonRt.getEffective(layer.id, layer.buttonDisabled) if (cur === 'normal') buttonRt.setState(layer.id, 'hover') } function onButtonLeave(layer: Layer) { + setStageCursor('') if (!stagePreview.value) return const cur = buttonRt.getEffective(layer.id, layer.buttonDisabled) if (cur === 'hover' || cur === 'pressed') buttonRt.setState(layer.id, 'normal') @@ -543,12 +553,12 @@ function buttonAssetFor(asset: Asset): ButtonAsset | null { return asset.kind === 'button' ? asset : null } -function buttonStateFor(layer: Layer): ButtonState { +function buttonTimeFor(layer: Layer): number { // In edit mode the button is a static asset — always show its normal // state regardless of leftover runtime state (e.g. from a previous // preview session). - if (!stagePreview.value) return 'normal' - return buttonRt.getEffective(layer.id, layer.buttonDisabled) + if (!stagePreview.value) return BUTTON_STATE_TIMES.normal + return buttonRt.getEffectiveTime(layer.id, layer.buttonDisabled) } const stageConfig = computed(() => ({ @@ -2912,7 +2922,7 @@ onBeforeUnmount(() => { :config="buttonHitCatcherConfig(buttonAssetFor(entry.item.asset)!, entry.item.layer)" />