Merge latest main (v1.29.2) into bl-selective-snapshot-update
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
@@ -60,7 +60,6 @@ async function getInputLinkDetails(
|
||||
)
|
||||
}
|
||||
|
||||
// Test helpers to reduce repetition across cases
|
||||
function slotLocator(
|
||||
page: Page,
|
||||
nodeId: NodeId,
|
||||
@@ -789,6 +788,45 @@ test.describe('Vue Node Link Interaction', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('should batch disconnect all links with ctrl+alt+click on slot', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const clipNode = (await comfyPage.getNodeRefsByType('CLIPTextEncode'))[0]
|
||||
const samplerNode = (await comfyPage.getNodeRefsByType('KSampler'))[0]
|
||||
expect(clipNode && samplerNode).toBeTruthy()
|
||||
|
||||
await connectSlots(
|
||||
comfyPage.page,
|
||||
{ nodeId: clipNode.id, index: 0 },
|
||||
{ nodeId: samplerNode.id, index: 1 },
|
||||
() => comfyPage.nextFrame()
|
||||
)
|
||||
await connectSlots(
|
||||
comfyPage.page,
|
||||
{ nodeId: clipNode.id, index: 0 },
|
||||
{ nodeId: samplerNode.id, index: 2 },
|
||||
() => comfyPage.nextFrame()
|
||||
)
|
||||
|
||||
const clipOutput = await clipNode.getOutput(0)
|
||||
expect(await clipOutput.getLinkCount()).toBe(2)
|
||||
|
||||
const clipOutputSlot = slotLocator(comfyPage.page, clipNode.id, 0, false)
|
||||
|
||||
await clipOutputSlot.dispatchEvent('pointerdown', {
|
||||
button: 0,
|
||||
buttons: 1,
|
||||
ctrlKey: true,
|
||||
altKey: true,
|
||||
shiftKey: false,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
})
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
expect(await clipOutput.getLinkCount()).toBe(0)
|
||||
})
|
||||
|
||||
test.describe('Release actions (Shift-drop)', () => {
|
||||
test('Context menu opens and endpoint is pinned on Shift-drop', async ({
|
||||
comfyPage,
|
||||
|
||||
|
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 |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"private": true,
|
||||
"version": "1.29.1",
|
||||
"version": "1.29.2",
|
||||
"type": "module",
|
||||
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
||||
"homepage": "https://comfy.org",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -178,8 +178,9 @@ app.registerExtension({
|
||||
audioUIWidget.options.canvasOnly = true
|
||||
|
||||
const onAudioWidgetUpdate = () => {
|
||||
if (typeof audioWidget.value !== 'string') return
|
||||
audioUIWidget.element.src = api.apiURL(
|
||||
getResourceURL(...splitFilePath(audioWidget.value as string))
|
||||
getResourceURL(...splitFilePath(audioWidget.value))
|
||||
)
|
||||
}
|
||||
// Initially load default audio file to audioUIWidget.
|
||||
|
||||
@@ -832,6 +832,11 @@
|
||||
"guidance": {
|
||||
"name": "guidance"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"CLIPTextEncodeHiDream": {
|
||||
@@ -871,6 +876,11 @@
|
||||
"mt5xl": {
|
||||
"name": "mt5xl"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"CLIPTextEncodeLumina2": {
|
||||
@@ -937,6 +947,11 @@
|
||||
"empty_padding": {
|
||||
"name": "empty_padding"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"CLIPTextEncodeSDXL": {
|
||||
@@ -1475,10 +1490,12 @@
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "positive"
|
||||
"name": "positive",
|
||||
"tooltip": null
|
||||
},
|
||||
"1": {
|
||||
"name": "negative"
|
||||
"name": "negative",
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1974,6 +1991,11 @@
|
||||
"batch_size": {
|
||||
"name": "batch_size"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"EmptyHunyuanLatentVideo": {
|
||||
@@ -1991,6 +2013,11 @@
|
||||
"batch_size": {
|
||||
"name": "batch_size"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"EmptyImage": {
|
||||
@@ -2113,6 +2140,11 @@
|
||||
"batch_size": {
|
||||
"name": "batch_size"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"Epsilon Scaling": {
|
||||
@@ -2200,6 +2232,11 @@
|
||||
"conditioning": {
|
||||
"name": "conditioning"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"FluxGuidance": {
|
||||
@@ -2211,6 +2248,11 @@
|
||||
"guidance": {
|
||||
"name": "guidance"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"FluxKontextImageScale": {
|
||||
@@ -2220,6 +2262,11 @@
|
||||
"image": {
|
||||
"name": "image"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"FluxKontextMaxImageNode": {
|
||||
@@ -2272,6 +2319,11 @@
|
||||
"reference_latents_method": {
|
||||
"name": "reference_latents_method"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"FluxKontextProImageNode": {
|
||||
@@ -2629,6 +2681,10 @@
|
||||
"name": "files",
|
||||
"tooltip": "Optional file(s) to use as context for the model. Accepts inputs from the Gemini Generate Content Input Files node."
|
||||
},
|
||||
"aspect_ratio": {
|
||||
"name": "aspect_ratio",
|
||||
"tooltip": "Defaults to matching the output image size to that of your input image, or otherwise generates 1:1 squares."
|
||||
},
|
||||
"control_after_generate": {
|
||||
"name": "control after generate"
|
||||
}
|
||||
@@ -2870,10 +2926,12 @@
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "positive"
|
||||
"name": "positive",
|
||||
"tooltip": null
|
||||
},
|
||||
"1": {
|
||||
"name": "latent"
|
||||
"name": "latent",
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2895,13 +2953,16 @@
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "positive"
|
||||
"name": "positive",
|
||||
"tooltip": null
|
||||
},
|
||||
"1": {
|
||||
"name": "negative"
|
||||
"name": "negative",
|
||||
"tooltip": null
|
||||
},
|
||||
"2": {
|
||||
"name": "latent"
|
||||
"name": "latent",
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3478,6 +3539,11 @@
|
||||
"image": {
|
||||
"name": "image"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"ImageYUVToRGB": {
|
||||
@@ -3582,6 +3648,11 @@
|
||||
"alpha": {
|
||||
"name": "alpha"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"KarrasScheduler": {
|
||||
@@ -4209,6 +4280,11 @@
|
||||
"samples2": {
|
||||
"name": "samples2"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"LatentApplyOperation": {
|
||||
@@ -4220,6 +4296,11 @@
|
||||
"operation": {
|
||||
"name": "operation"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"LatentApplyOperationCFG": {
|
||||
@@ -4231,6 +4312,11 @@
|
||||
"operation": {
|
||||
"name": "operation"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"LatentBatch": {
|
||||
@@ -4242,6 +4328,11 @@
|
||||
"samples2": {
|
||||
"name": "samples2"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"LatentBatchSeedBehavior": {
|
||||
@@ -4253,6 +4344,11 @@
|
||||
"seed_behavior": {
|
||||
"name": "seed_behavior"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"LatentBlend": {
|
||||
@@ -4324,6 +4420,11 @@
|
||||
"dim": {
|
||||
"name": "dim"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"LatentCrop": {
|
||||
@@ -4361,6 +4462,11 @@
|
||||
"amount": {
|
||||
"name": "amount"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"LatentFlip": {
|
||||
@@ -4400,6 +4506,11 @@
|
||||
"ratio": {
|
||||
"name": "ratio"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"LatentMultiply": {
|
||||
@@ -4411,6 +4522,11 @@
|
||||
"multiplier": {
|
||||
"name": "multiplier"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"LatentOperationSharpen": {
|
||||
@@ -4425,6 +4541,11 @@
|
||||
"alpha": {
|
||||
"name": "alpha"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"LatentOperationTonemapReinhard": {
|
||||
@@ -4433,6 +4554,11 @@
|
||||
"multiplier": {
|
||||
"name": "multiplier"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"LatentRotate": {
|
||||
@@ -4455,6 +4581,11 @@
|
||||
"samples2": {
|
||||
"name": "samples2"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"LatentUpscale": {
|
||||
@@ -7701,6 +7832,10 @@
|
||||
"name": "video",
|
||||
"tooltip": "The reference video used to generate the output video. Must be at least 5 seconds long. Videos longer than 5s will be automatically trimmed. Only MP4 format supported."
|
||||
},
|
||||
"steps": {
|
||||
"name": "steps",
|
||||
"tooltip": "Number of inference steps"
|
||||
},
|
||||
"control_type": {
|
||||
"name": "control_type"
|
||||
},
|
||||
@@ -8106,6 +8241,11 @@
|
||||
"upscale_method": {
|
||||
"name": "upscale_method"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"PerpNeg": {
|
||||
@@ -8407,7 +8547,7 @@
|
||||
},
|
||||
"mask": {
|
||||
"name": "mask",
|
||||
"tooltip": "Use the mask to define areas in the video to replace"
|
||||
"tooltip": "Use the mask to define areas in the video to replace."
|
||||
},
|
||||
"prompt_text": {
|
||||
"name": "prompt_text"
|
||||
@@ -8418,6 +8558,10 @@
|
||||
"seed": {
|
||||
"name": "seed"
|
||||
},
|
||||
"region_to_modify": {
|
||||
"name": "region_to_modify",
|
||||
"tooltip": "Plaintext description of the object / region to modify."
|
||||
},
|
||||
"control_after_generate": {
|
||||
"name": "control after generate"
|
||||
}
|
||||
@@ -8635,6 +8779,14 @@
|
||||
"mode": {
|
||||
"name": "mode"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
},
|
||||
"1": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"Preview3D": {
|
||||
@@ -10248,6 +10400,11 @@
|
||||
"rescaling_scale": {
|
||||
"name": "rescaling_scale"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"SkipLayerGuidanceDiTSimple": {
|
||||
@@ -10269,6 +10426,11 @@
|
||||
"end_percent": {
|
||||
"name": "end_percent"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"SkipLayerGuidanceSD3": {
|
||||
@@ -10290,6 +10452,11 @@
|
||||
"end_percent": {
|
||||
"name": "end_percent"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"SolidMask": {
|
||||
@@ -10329,6 +10496,14 @@
|
||||
"image": {
|
||||
"name": "image"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
},
|
||||
"1": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"SplitSigmas": {
|
||||
@@ -11152,6 +11327,11 @@
|
||||
"name": "image_interleave",
|
||||
"tooltip": "How much the image influences things vs the text prompt. Higher number means more influence from the text prompt."
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"TextEncodeQwenImageEdit": {
|
||||
@@ -11379,6 +11559,11 @@
|
||||
"clip_name3": {
|
||||
"name": "clip_name3"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"TripoConversionNode": {
|
||||
@@ -11760,6 +11945,11 @@
|
||||
"model_name": {
|
||||
"name": "model_name"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"USOStyleReference": {
|
||||
|
||||
@@ -595,6 +595,13 @@ export function useSlotLinkInteraction({
|
||||
event.altKey &&
|
||||
!event.shiftKey
|
||||
|
||||
const shouldBatchDisconnectOutputLinks =
|
||||
isOutputSlot &&
|
||||
hasExistingOutputLink &&
|
||||
ctrlOrMeta &&
|
||||
event.altKey &&
|
||||
!event.shiftKey
|
||||
|
||||
const existingInputLink =
|
||||
isInputSlot && inputLinkId != null
|
||||
? graph.getLink(inputLinkId)
|
||||
@@ -604,6 +611,14 @@ export function useSlotLinkInteraction({
|
||||
resolvedNode.disconnectInput(index, true)
|
||||
}
|
||||
|
||||
if (shouldBatchDisconnectOutputLinks && resolvedNode) {
|
||||
resolvedNode.disconnectOutput(index)
|
||||
app.canvas?.setDirty(true, true)
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return
|
||||
}
|
||||
|
||||
const baseDirection = isInputSlot
|
||||
? inputSlot?.dir ?? LinkDirection.LEFT
|
||||
: outputSlot?.dir ?? LinkDirection.RIGHT
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||