fix FLOAT widget incrementing broken & disabled state styles on widget number input (Vue) (#6036)
## Summary Align Vue node number widgets with Figma by centralising button styling and surfacing disabled-state tokens in the design system. ## Changes - **What**: Added shared [`useNumberWidgetButtonPt`](src/renderer/extensions/vueNodes/widgets/composables/useNumberWidgetButtonPt.ts) helper so both PrimeVue `InputNumber` widgets reuse the same Tailwind token classes, and added the `color/tokens/alpha` values in [`packages/design-system/src/css/style.css`](packages/design-system/src/css/style.css#L89-L212) so semantic aliases remain token-driven ([PrimeVue passthrough docs](https://www.primefaces.org/primevue/passthrough) w [`color-mix`](https://www.w3.org/TR/css-color-5/#color-mix)) ## Review Focus Confirm hover/active/disabled colours match the design tokens in both light and dark themes and that float precision still respects the safe-range guard. <img width="1377" height="1150" alt="Screenshot from 2025-10-12 17-53-23" src="https://github.com/user-attachments/assets/c7d34870-5d07-4ce1-9272-7def7ae813b6" /> <img width="1377" height="1150" alt="Screenshot from 2025-10-12 17-53-32" src="https://github.com/user-attachments/assets/86872ec8-979b-4586-879c-41a126a5f932" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6036-fix-disabled-state-styles-on-Vue-widget-number-input-INT-and-FLOAT-widgets-28b6d73d365081f8aef7fa860b641f7d) by [Unito](https://www.unito.io) --------- Co-authored-by: github-actions <github-actions@github.com>
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
@@ -89,6 +89,21 @@
|
||||
--color-node-hover-100: rgb(from var(--color-charcoal-100) r g b/ 0.15);
|
||||
--color-node-hover-200: rgb(from var(--color-charcoal-100) r g b/ 0.1);
|
||||
--color-modal-tag: rgb(from var(--color-gray-400) r g b/ 0.4);
|
||||
--color-alpha-charcoal-600-30: color-mix(
|
||||
in srgb,
|
||||
var(--color-charcoal-600) 30%,
|
||||
transparent
|
||||
);
|
||||
--color-alpha-stone-100-20: color-mix(
|
||||
in srgb,
|
||||
var(--color-stone-100) 20%,
|
||||
transparent
|
||||
);
|
||||
--color-alpha-gray-500-50: color-mix(
|
||||
in srgb,
|
||||
var(--color-gray-500) 50%,
|
||||
transparent
|
||||
);
|
||||
|
||||
/* PrimeVue pulled colors */
|
||||
--color-muted: var(--p-text-muted-color);
|
||||
@@ -155,6 +170,8 @@
|
||||
from var(--color-zinc-500) r g b / 10%
|
||||
);
|
||||
--node-component-widget-skeleton-surface: var(--color-zinc-300);
|
||||
--node-component-disabled: var(--color-alpha-stone-100-20);
|
||||
--node-icon-disabled: var(--color-alpha-gray-500-50);
|
||||
--node-stroke: var(--color-gray-400);
|
||||
--node-stroke-selected: var(--color-accent-primary);
|
||||
--node-stroke-error: var(--color-error);
|
||||
@@ -184,6 +201,8 @@
|
||||
--node-component-tooltip-border: var(--color-slate-300);
|
||||
--node-component-tooltip-surface: var(--color-charcoal-800);
|
||||
--node-component-widget-skeleton-surface: var(--color-zinc-800);
|
||||
--node-component-disabled: var(--color-alpha-charcoal-600-30);
|
||||
--node-icon-disabled: var(--color-alpha-stone-100-20);
|
||||
--node-stroke: var(--color-stone-200);
|
||||
--node-stroke-selected: var(--color-pure-white);
|
||||
--node-stroke-error: var(--color-error);
|
||||
@@ -224,6 +243,8 @@
|
||||
--color-node-component-widget-skeleton-surface: var(
|
||||
--node-component-widget-skeleton-surface
|
||||
);
|
||||
--color-node-component-disabled: var(--node-component-disabled);
|
||||
--color-node-icon-disabled: var(--node-icon-disabled);
|
||||
--color-node-stroke: var(--node-stroke);
|
||||
--color-node-stroke-selected: var(--node-stroke-selected);
|
||||
--color-node-stroke-error: var(--node-stroke-error);
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
filterWidgetProps
|
||||
} from '@/utils/widgetPropFilter'
|
||||
|
||||
import { useNumberWidgetButtonPt } from '../composables/useNumberWidgetButtonPt'
|
||||
import { WidgetInputBaseClass } from './layout'
|
||||
import WidgetLayoutField from './layout/WidgetLayoutField.vue'
|
||||
|
||||
@@ -65,17 +66,24 @@ const useGrouping = computed(() => {
|
||||
|
||||
// Check if increment/decrement buttons should be disabled due to precision limits
|
||||
const buttonsDisabled = computed(() => {
|
||||
const currentValue = localValue.value || 0
|
||||
return !Number.isSafeInteger(currentValue)
|
||||
const currentValue = localValue.value ?? 0
|
||||
return (
|
||||
!Number.isFinite(currentValue) ||
|
||||
Math.abs(currentValue) > Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
})
|
||||
|
||||
// Tooltip message for disabled buttons
|
||||
const buttonTooltip = computed(() => {
|
||||
if (buttonsDisabled.value) {
|
||||
return 'Increment/decrement disabled: value exceeds JavaScript precision limit (±2^53)'
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
const inputNumberPt = useNumberWidgetButtonPt({
|
||||
roundedLeft: true,
|
||||
roundedRight: true
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -84,19 +92,14 @@ const buttonTooltip = computed(() => {
|
||||
<InputNumber
|
||||
v-model="localValue"
|
||||
v-bind="filteredProps"
|
||||
:show-buttons="!buttonsDisabled"
|
||||
button-layout="horizontal"
|
||||
size="small"
|
||||
:step="stepValue"
|
||||
:use-grouping="useGrouping"
|
||||
:class="cn(WidgetInputBaseClass, 'w-full text-xs')"
|
||||
:aria-label="widget.name"
|
||||
:pt="{
|
||||
incrementButton:
|
||||
'!rounded-r-lg bg-transparent border-none hover:bg-zinc-500/30 active:bg-zinc-500/40',
|
||||
decrementButton:
|
||||
'!rounded-l-lg bg-transparent border-none hover:bg-zinc-500/30 active:bg-zinc-500/40'
|
||||
}"
|
||||
:show-buttons="!buttonsDisabled"
|
||||
:pt="inputNumberPt"
|
||||
@update:model-value="onChange"
|
||||
>
|
||||
<template #incrementicon>
|
||||
@@ -120,4 +123,9 @@ const buttonTooltip = computed(() => {
|
||||
margin: 1px 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:deep(.p-inputnumber-button.p-disabled .pi),
|
||||
:deep(.p-inputnumber-button.p-disabled .p-icon) {
|
||||
color: var(--color-node-icon-disabled) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
size="small"
|
||||
pt:pc-input-text:root="min-w-full bg-transparent border-none text-center"
|
||||
class="w-16"
|
||||
:show-buttons="!buttonsDisabled"
|
||||
:pt="sliderNumberPt"
|
||||
@update:model-value="handleNumberInputUpdate"
|
||||
/>
|
||||
</div>
|
||||
@@ -43,6 +45,7 @@ import {
|
||||
filterWidgetProps
|
||||
} from '@/utils/widgetPropFilter'
|
||||
|
||||
import { useNumberWidgetButtonPt } from '../composables/useNumberWidgetButtonPt'
|
||||
import { WidgetInputBaseClass } from './layout'
|
||||
import WidgetLayoutField from './layout/WidgetLayoutField.vue'
|
||||
|
||||
@@ -103,4 +106,24 @@ const stepValue = computed(() => {
|
||||
// precision 1 → 0.1, precision 2 → 0.01, etc.
|
||||
return 1 / Math.pow(10, precision.value)
|
||||
})
|
||||
|
||||
const buttonsDisabled = computed(() => {
|
||||
const currentValue = localValue.value ?? 0
|
||||
return (
|
||||
!Number.isFinite(currentValue) ||
|
||||
Math.abs(currentValue) > Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
})
|
||||
|
||||
const sliderNumberPt = useNumberWidgetButtonPt({
|
||||
roundedLeft: true,
|
||||
roundedRight: true
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.p-inputnumber-button.p-disabled .pi),
|
||||
:deep(.p-inputnumber-button.p-disabled .p-icon) {
|
||||
color: var(--color-node-icon-disabled) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
const sharedButtonClasses =
|
||||
'!inline-flex !items-center !justify-center !border-0 !bg-transparent text-inherit transition-colors duration-150 ease-in-out ' +
|
||||
'hover:!bg-[var(--color-node-component-surface-hovered)] active:!bg-[var(--color-node-component-surface-selected)] ' +
|
||||
'disabled:!bg-[var(--color-node-component-disabled)] disabled:!text-[var(--color-node-icon-disabled)] disabled:cursor-not-allowed'
|
||||
|
||||
export function useNumberWidgetButtonPt(options?: {
|
||||
roundedLeft?: boolean
|
||||
roundedRight?: boolean
|
||||
}) {
|
||||
const { roundedLeft = false, roundedRight = false } = options ?? {}
|
||||
|
||||
const increment = `${sharedButtonClasses}${roundedRight ? ' !rounded-r-lg' : ''}`
|
||||
const decrement = `${sharedButtonClasses}${roundedLeft ? ' !rounded-l-lg' : ''}`
|
||||
|
||||
return {
|
||||
incrementButton: {
|
||||
class: increment.trim()
|
||||
},
|
||||
decrementButton: {
|
||||
class: decrement.trim()
|
||||
}
|
||||
}
|
||||
}
|
||||