diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000..ae90f70514 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +ignore-workspace-root-check=true diff --git a/components.json b/components.json new file mode 100644 index 0000000000..5526f900d5 --- /dev/null +++ b/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://shadcn-vue.com/schema.json", + "style": "new-york", + "typescript": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "src/assets/css/style.css", + "baseColor": "stone", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "composables": "@/composables", + "utils": "@/utils", + "ui": "@/components/ui", + "lib": "@/lib" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js index 7e3248b20a..cddba3bbd3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -64,6 +64,9 @@ export default [ 'vue/no-v-html': 'off', // Enforce dark-theme: instead of dark: prefix 'vue/no-restricted-class': ['error', '/^dark:/'], + 'vue/multi-word-component-names': 'off', // TODO: fix + 'vue/no-template-shadow': 'off', // TODO: fix + 'vue/one-component-per-file': 'off', // TODO: fix // Restrict deprecated PrimeVue components 'no-restricted-imports': [ 'error', diff --git a/package.json b/package.json index f4e6035d3a..7c03496a49 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "tailwindcss": "^4.1.12", "tailwindcss-primeui": "^0.6.1", "tsx": "^4.15.6", + "tw-animate-css": "^1.3.8", "typescript": "^5.4.5", "typescript-eslint": "^8.42.0", "unplugin-icons": "^0.22.0", @@ -140,6 +141,7 @@ "pinia": "^2.1.7", "primeicons": "^7.0.0", "primevue": "^4.2.5", + "reka-ui": "^2.5.0", "semver": "^7.7.2", "tailwind-merge": "^3.3.1", "three": "^0.170.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68fbf59b22..58c7d7aa41 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -134,6 +134,9 @@ importers: primevue: specifier: ^4.2.5 version: 4.2.5(vue@3.5.13(typescript@5.9.2)) + reka-ui: + specifier: ^2.5.0 + version: 2.5.0(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2)) semver: specifier: ^7.7.2 version: 7.7.2 @@ -303,6 +306,9 @@ importers: tsx: specifier: ^4.15.6 version: 4.19.4 + tw-animate-css: + specifier: ^1.3.8 + version: 1.3.8 typescript: specifier: ^5.4.5 version: 5.9.2 @@ -1570,6 +1576,18 @@ packages: '@firebase/webchannel-wrapper@1.0.3': resolution: {integrity: sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ==} + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@floating-ui/vue@1.1.9': + resolution: {integrity: sha512-BfNqNW6KA83Nexspgb9DZuz578R7HT8MZw1CfK9I6Ah4QReNWEJsXWHN+SdmOVLNGmTPDi+fDT535Df5PzMLbQ==} + '@grpc/grpc-js@1.9.15': resolution: {integrity: sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==} engines: {node: ^8.13.0 || >=10.10.0} @@ -1610,6 +1628,12 @@ packages: '@iconify/utils@2.3.0': resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==} + '@internationalized/date@3.9.0': + resolution: {integrity: sha512-yaN3brAnHRD+4KyyOsJyk49XUvj2wtbNACSqg0bz3u8t2VuzhC8Q5dfRnrSxjnnbDb+ienBnkn1TzQfE154vyg==} + + '@internationalized/number@3.6.5': + resolution: {integrity: sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==} + '@intlify/core-base@9.14.3': resolution: {integrity: sha512-nbJ7pKTlXFnaXPblyfiH6awAx1C0PWNNuqXAR74yRwgi5A/Re/8/5fErLY0pv4R8+EHj3ZaThMHdnuC/5OBa6g==} engines: {node: '>= 16'} @@ -2243,6 +2267,9 @@ packages: storybook: ^9.1.1 vue: ^3.0.0 + '@swc/helpers@0.5.17': + resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} + '@tailwindcss/node@4.1.12': resolution: {integrity: sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==} @@ -2333,6 +2360,14 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 + '@tanstack/virtual-core@3.13.12': + resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==} + + '@tanstack/vue-virtual@3.13.12': + resolution: {integrity: sha512-vhF7kEU9EXWXh+HdAwKJ2m3xaOnTTmgcdXcF2pim8g4GvI7eRrk2YRuV5nUlZnd/NbCIX4/Ja2OZu5EjJL06Ww==} + peerDependencies: + vue: ^2.7.0 || ^3.0.0 + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -2609,6 +2644,9 @@ packages: '@types/web-bluetooth@0.0.20': resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + '@types/webxr@0.5.20': resolution: {integrity: sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==} @@ -2859,12 +2897,21 @@ packages: '@vueuse/core@11.0.0': resolution: {integrity: sha512-shibzNGjmRjZucEm97B8V0NO5J3vPHMCE/mltxQ3vHezbDoFQBMtK11XsfwfPionxSbo+buqPmsCljtYuXIBpw==} + '@vueuse/core@12.8.2': + resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==} + '@vueuse/metadata@11.0.0': resolution: {integrity: sha512-0TKsAVT0iUOAPWyc9N79xWYfovJVPATiOPVKByG6jmAYdDiwvMVm9xXJ5hp4I8nZDxpCcYlLq/Rg9w1Z/jrGcg==} + '@vueuse/metadata@12.8.2': + resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==} + '@vueuse/shared@11.0.0': resolution: {integrity: sha512-i4ZmOrIEjSsL94uAEt3hz88UCz93fMyP/fba9S+vypX90fKg3uYX9cThqvWc9aXxuTzR0UGhOKOTQd//Goh1nQ==} + '@vueuse/shared@12.8.2': + resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==} + '@webgpu/types@0.1.51': resolution: {integrity: sha512-ktR3u64NPjwIViNCck+z9QeyN0iPkQCUOQ07ZCV1RzlkfP+olLTeEZ95O1QHS+v4w9vJeY9xj/uJuSphsHy5rQ==} @@ -3018,6 +3065,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} @@ -3488,6 +3539,9 @@ packages: resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} engines: {node: '>=12'} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -5105,6 +5159,9 @@ packages: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -5555,6 +5612,11 @@ packages: resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==} hasBin: true + reka-ui@2.5.0: + resolution: {integrity: sha512-81aMAmJeVCy2k0E6x7n1kypDY6aM1ldLis5+zcdV1/JtoAlSDck5OBsyLRJU9CfgbrQp1ImnRnBSmC4fZ2fkZQ==} + peerDependencies: + vue: '>= 3.2.0' + relateurl@0.2.7: resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} engines: {node: '>= 0.10'} @@ -5989,6 +6051,9 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tw-animate-css@1.3.8: + resolution: {integrity: sha512-Qrk3PZ7l7wUcGYhwZloqfkWCmaXZAoqjkdbIDvzfGshwGtexa/DAs9koXxIkrpEasyevandomzCBAV1Yyop5rw==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -7999,6 +8064,26 @@ snapshots: '@firebase/webchannel-wrapper@1.0.3': {} + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/utils@0.2.10': {} + + '@floating-ui/vue@1.1.9(vue@3.5.13(typescript@5.9.2))': + dependencies: + '@floating-ui/dom': 1.7.4 + '@floating-ui/utils': 0.2.10 + vue-demi: 0.14.10(vue@3.5.13(typescript@5.9.2)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + '@grpc/grpc-js@1.9.15': dependencies: '@grpc/proto-loader': 0.7.13 @@ -8050,6 +8135,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@internationalized/date@3.9.0': + dependencies: + '@swc/helpers': 0.5.17 + + '@internationalized/number@3.6.5': + dependencies: + '@swc/helpers': 0.5.17 + '@intlify/core-base@9.14.3': dependencies: '@intlify/message-compiler': 9.14.3 @@ -8834,6 +8927,10 @@ snapshots: vue: 3.5.13(typescript@5.9.2) vue-component-type-helpers: 3.0.6 + '@swc/helpers@0.5.17': + dependencies: + tslib: 2.8.1 + '@tailwindcss/node@4.1.12': dependencies: '@jridgewell/remapping': 2.3.5 @@ -8905,6 +9002,13 @@ snapshots: tailwindcss: 4.1.12 vite: 5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2) + '@tanstack/virtual-core@3.13.12': {} + + '@tanstack/vue-virtual@3.13.12(vue@3.5.13(typescript@5.9.2))': + dependencies: + '@tanstack/virtual-core': 3.13.12 + vue: 3.5.13(typescript@5.9.2) + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.27.1 @@ -9212,6 +9316,8 @@ snapshots: '@types/web-bluetooth@0.0.20': {} + '@types/web-bluetooth@0.0.21': {} + '@types/webxr@0.5.20': {} '@typescript-eslint/eslint-plugin@8.42.0(@typescript-eslint/parser@8.42.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)': @@ -9618,8 +9724,19 @@ snapshots: - '@vue/composition-api' - vue + '@vueuse/core@12.8.2(typescript@5.9.2)': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 12.8.2 + '@vueuse/shared': 12.8.2(typescript@5.9.2) + vue: 3.5.13(typescript@5.9.2) + transitivePeerDependencies: + - typescript + '@vueuse/metadata@11.0.0': {} + '@vueuse/metadata@12.8.2': {} + '@vueuse/shared@11.0.0(vue@3.5.13(typescript@5.9.2))': dependencies: vue-demi: 0.14.10(vue@3.5.13(typescript@5.9.2)) @@ -9627,6 +9744,12 @@ snapshots: - '@vue/composition-api' - vue + '@vueuse/shared@12.8.2(typescript@5.9.2)': + dependencies: + vue: 3.5.13(typescript@5.9.2) + transitivePeerDependencies: + - typescript + '@webgpu/types@0.1.51': {} '@xterm/addon-fit@0.10.0(@xterm/xterm@5.5.0)': @@ -9775,6 +9898,10 @@ snapshots: argparse@2.0.1: {} + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + aria-query@5.3.0: dependencies: dequal: 2.0.3 @@ -10244,6 +10371,8 @@ snapshots: define-lazy-prop@3.0.0: {} + defu@6.1.4: {} + delayed-stream@1.0.0: {} dequal@2.0.3: {} @@ -12135,6 +12264,8 @@ snapshots: object-keys@1.1.1: {} + ohash@2.0.11: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -12711,6 +12842,23 @@ snapshots: dependencies: jsesc: 3.0.2 + reka-ui@2.5.0(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2)): + dependencies: + '@floating-ui/dom': 1.7.4 + '@floating-ui/vue': 1.1.9(vue@3.5.13(typescript@5.9.2)) + '@internationalized/date': 3.9.0 + '@internationalized/number': 3.6.5 + '@tanstack/vue-virtual': 3.13.12(vue@3.5.13(typescript@5.9.2)) + '@vueuse/core': 12.8.2(typescript@5.9.2) + '@vueuse/shared': 12.8.2(typescript@5.9.2) + aria-hidden: 1.2.6 + defu: 6.1.4 + ohash: 2.0.11 + vue: 3.5.13(typescript@5.9.2) + transitivePeerDependencies: + - '@vue/composition-api' + - typescript + relateurl@0.2.7: {} remark-frontmatter@5.0.0: @@ -13158,6 +13306,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tw-animate-css@1.3.8: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 diff --git a/src/assets/css/style.css b/src/assets/css/style.css index 3258cbba50..9c49e17046 100644 --- a/src/assets/css/style.css +++ b/src/assets/css/style.css @@ -2,8 +2,9 @@ @import 'tailwindcss/theme' layer(theme); @import 'tailwindcss/utilities' layer(utilities); +@import 'tw-animate-css'; -@plugin "tailwindcss-primeui"; +@plugin 'tailwindcss-primeui'; @config '../../../tailwind.config.ts'; @@ -114,6 +115,14 @@ --color-dark-elevation-2: rgba(from white r g b / 0.03); } +@theme inline { + --color-node-component-surface: var(--color-charcoal-300); + --color-node-component-surface-highlight: var(--color-slate-100); + --color-node-component-surface-hovered: var(--color-charcoal-500); + --color-node-component-surface-selected: var(--color-charcoal-700); + --color-node-stroke: var(--color-stone-100); +} + @custom-variant dark-theme { .dark-theme & { @slot; diff --git a/src/components/ui/slider/Slider.vue b/src/components/ui/slider/Slider.vue new file mode 100644 index 0000000000..3ce2eb3d84 --- /dev/null +++ b/src/components/ui/slider/Slider.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.test.ts index 179c342d20..828029e89d 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.test.ts @@ -1,62 +1,63 @@ import { mount } from '@vue/test-utils' import PrimeVue from 'primevue/config' -import InputText from 'primevue/inputtext' -import Slider from 'primevue/slider' -import type { SliderProps } from 'primevue/slider' +import InputNumber from 'primevue/inputnumber' import { describe, expect, it } from 'vitest' +import Slider from '@/components/ui/slider/Slider.vue' import type { SimplifiedWidget } from '@/types/simplifiedWidget' import WidgetInputNumberSlider from './WidgetInputNumberSlider.vue' -describe('WidgetInputNumberSlider Value Binding', () => { - const createMockWidget = ( - value: number = 5, - options: Partial = {}, - callback?: (value: number) => void - ): SimplifiedWidget => ({ +function createMockWidget( + value: number = 5, + options: SimplifiedWidget['options'] = {}, + callback?: (value: number) => void +): SimplifiedWidget { + return { name: 'test_slider', type: 'float', value, options: { min: 0, max: 100, step: 1, precision: 0, ...options }, callback - }) - - const mountComponent = ( - widget: SimplifiedWidget, - modelValue: number, - readonly = false - ) => { - return mount(WidgetInputNumberSlider, { - global: { - plugins: [PrimeVue], - components: { InputText, Slider } - }, - props: { - widget, - modelValue, - readonly - } - }) } - - const getNumberInput = (wrapper: ReturnType) => { - const input = wrapper.find('input[type="number"]') - if (!(input.element instanceof HTMLInputElement)) { - throw new Error( - 'Number input element not found or is not an HTMLInputElement' - ) +} + +function mountComponent( + widget: SimplifiedWidget, + modelValue: number, + readonly = false +) { + return mount(WidgetInputNumberSlider, { + global: { + plugins: [PrimeVue], + components: { InputNumber, Slider } + }, + props: { + widget, + modelValue, + readonly } - return { element: input.element } + }) +} + +function getNumberInput(wrapper: ReturnType) { + const input = wrapper.find('input[inputmode="numeric"]') + if (!(input.element instanceof HTMLInputElement)) { + throw new Error( + 'Number input element not found or is not an HTMLInputElement' + ) } + return input.element +} +describe('WidgetInputNumberSlider Value Binding', () => { describe('Props and Values', () => { it('passes modelValue to slider component', () => { const widget = createMockWidget(5) const wrapper = mountComponent(widget, 5) const slider = wrapper.findComponent({ name: 'Slider' }) - expect(slider.props('modelValue')).toBe(5) + expect(slider.props('modelValue')).toEqual([5]) }) it('handles different initial values', () => { @@ -67,10 +68,10 @@ describe('WidgetInputNumberSlider Value Binding', () => { const wrapper2 = mountComponent(widget2, 10) const slider1 = wrapper1.findComponent({ name: 'Slider' }) - expect(slider1.props('modelValue')).toBe(5) + expect(slider1.props('modelValue')).toEqual([5]) const slider2 = wrapper2.findComponent({ name: 'Slider' }) - expect(slider2.props('modelValue')).toBe(10) + expect(slider2.props('modelValue')).toEqual([10]) }) }) @@ -85,8 +86,9 @@ describe('WidgetInputNumberSlider Value Binding', () => { it('renders input field', () => { const widget = createMockWidget(5) const wrapper = mountComponent(widget, 5) + console.log(wrapper.html()) - expect(wrapper.find('input[type="number"]').exists()).toBe(true) + expect(wrapper.find('input[inputmode="numeric"]').exists()).toBe(true) }) it('displays initial value in input field', () => { @@ -94,7 +96,7 @@ describe('WidgetInputNumberSlider Value Binding', () => { const wrapper = mountComponent(widget, 42) const input = getNumberInput(wrapper) - expect(input.element.value).toBe('42') + expect(input.value).toBe('42') }) it('disables components in readonly mode', () => { @@ -105,7 +107,7 @@ describe('WidgetInputNumberSlider Value Binding', () => { expect(slider.props('disabled')).toBe(true) const input = getNumberInput(wrapper) - expect(input.element.disabled).toBe(true) + expect(input.disabled).toBe(true) }) }) @@ -127,5 +129,47 @@ describe('WidgetInputNumberSlider Value Binding', () => { expect(slider.props('min')).toBe(-100) expect(slider.props('max')).toBe(100) }) + + describe('Step Size', () => { + it('should default to 1', () => { + const widget = createMockWidget(5) + const wrapper = mountComponent(widget, 5) + + const slider = wrapper.findComponent({ name: 'Slider' }) + expect(slider.props('step')).toBe(1) + }) + + it('should get the step2 value if present', () => { + const widget = createMockWidget(5, { step2: 0.01 }) + const wrapper = mountComponent(widget, 5) + + const slider = wrapper.findComponent({ name: 'Slider' }) + expect(slider.props('step')).toBe(0.01) + }) + + it('should be 1 for precision 0', () => { + const widget = createMockWidget(5, { precision: 0 }) + const wrapper = mountComponent(widget, 5) + + const slider = wrapper.findComponent({ name: 'Slider' }) + expect(slider.props('step')).toBe(1) + }) + + it('should be .1 for precision 1', () => { + const widget = createMockWidget(5, { precision: 1 }) + const wrapper = mountComponent(widget, 5) + + const slider = wrapper.findComponent({ name: 'Slider' }) + expect(slider.props('step')).toBe(0.1) + }) + + it('should be .00001 for precision 5', () => { + const widget = createMockWidget(5, { precision: 5 }) + const wrapper = mountComponent(widget, 5) + + const slider = wrapper.findComponent({ name: 'Slider' }) + expect(slider.props('step')).toBe(0.00001) + }) + }) }) }) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue index c9894f4b83..97cfd36061 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue @@ -6,31 +6,35 @@ " > - - - diff --git a/src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue b/src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue index 5d7e46f5d6..268a7c097a 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue @@ -19,7 +19,7 @@ defineProps<{ {{ widget.name }}