diff --git a/semcore/core/src/core-types/Component.ts b/semcore/core/src/core-types/Component.ts index 7a0defac38..e0cad8689b 100644 --- a/semcore/core/src/core-types/Component.ts +++ b/semcore/core/src/core-types/Component.ts @@ -52,12 +52,19 @@ type UncontrolledPropValue = | ((value: V, e?: any) => void | boolean | V)[] | ((e?: any) => void | boolean | V); +type StripDefaultPrefix = K extends `default${infer Rest}` ? Uncapitalize : K; + export abstract class Component< Props = {}, Enhance extends readonly ((...args: any[]) => any)[] = [], Uncontrolled extends Readonly<{ [key in keyof Props]?: UncontrolledPropValue }> = never, InnerProps = {}, State = {}, + DefaultProps extends { + [K in keyof DefaultProps]: StripDefaultPrefix extends keyof Props + ? Props[StripDefaultPrefix] + : never; + } = {}, > extends PureComponent { protected uncontrolledProps(): [Uncontrolled] extends [never] ? never : Uncontrolled { // @ts-ignore. This is a default value. Should be defined in related classes. @@ -79,7 +86,12 @@ export abstract class Component< return {} as Readonly< { Root: RootResult } & BaseAsProps & - Intergalactic.InternalTypings.EfficientOmit, keyof BaseAsProps> + Intergalactic.InternalTypings.EfficientOmit, keyof BaseAsProps> & + { + [DefaultPropKey in keyof DefaultProps as StripDefaultPrefix]: StripDefaultPrefix extends keyof Props + ? Exclude], undefined> + : never; + } >; } diff --git a/semcore/slider/src/Slider.tsx b/semcore/slider/src/Slider.tsx index 8a051faca0..e5b58ba866 100644 --- a/semcore/slider/src/Slider.tsx +++ b/semcore/slider/src/Slider.tsx @@ -7,17 +7,7 @@ import React from 'react'; import type { NSSlider } from './Slider.type'; import style from './style/slider.shadow.css'; -const FALLBACK_MIN_VALUE = 0; -const FALLBACK_MAX_VALUE = 100; -const FALLBACK_STEP_VALUE = 1; - -const getNumericValue = ({ value, fallback }: { value?: NSSlider.Value; fallback: number }): number => { - if (!value) return fallback; - - const numericValue = Number(value); - - return isNaN(numericValue) ? fallback : numericValue; -}; +const FALLBACK_VALUE = 0; const convertValueToPercent = (value: number, min: number, max: number) => { if (value > max) return 100; @@ -25,32 +15,61 @@ const convertValueToPercent = (value: number, min: number, max: number) => { return ((value - min) / (max - min)) * 100; }; +type StripDefaultPrefix = K extends `default${infer Rest}` ? Uncapitalize : K; +type DefaultProps = { + [K in keyof DP]: StripDefaultPrefix extends keyof P + ? P[StripDefaultPrefix] + : never +}; + +type DefaultPropsValue = [P] extends [never] + ? never + : [DP] extends [never] + ? never + : DefaultProps | ((props: P) => DefaultProps); + +export function withDefaultProps< + P = never, + DP = never, +>(value: NoInfer>) { + return function any>( + target: T, + _context: ClassDecoratorContext, + ) { + return class extends target { + static defaultProps = value; + }; + }; +} + +@withDefaultProps(() => ({ + defaultValue: 0, + min: 0, + max: 100, + step: 1, + children: ( + <> + + + + + + + ), +})) class SliderRoot extends Component< - Intergalactic.InternalTypings.InferComponentProps, - [], - NSSlider.Handlers -> { + Intergalactic.InternalTypings.InferComponentProps, + [], + NSSlider.Handlers, + {}, + {}, + NSSlider.DefaultProps + > { static displayName = 'Slider'; static style = style; sliderRef = React.createRef() as React.MutableRefObject; - static defaultProps = () => ({ - defaultValue: FALLBACK_MIN_VALUE, - min: FALLBACK_MIN_VALUE, - max: FALLBACK_MAX_VALUE, - step: FALLBACK_STEP_VALUE, - children: ( - <> - - - - - - - ), - }); - handleRef = (node: HTMLButtonElement) => { this.sliderRef.current = node; }; @@ -66,8 +85,8 @@ class SliderRoot extends Component< return { value: this.getNumericValue(), - min: getNumericValue({ value: min, fallback: FALLBACK_MIN_VALUE }), - max: getNumericValue({ value: max, fallback: FALLBACK_MAX_VALUE }), + min, + max, disabled, options, }; @@ -78,8 +97,8 @@ class SliderRoot extends Component< return { value: this.getNumericValue(), - min: getNumericValue({ value: min, fallback: FALLBACK_MIN_VALUE }), - max: getNumericValue({ value: max, fallback: FALLBACK_MAX_VALUE }), + min, + max, disabled, options, }; @@ -134,7 +153,7 @@ class SliderRoot extends Component< const lastOption = options?.[options?.length - 1]; const resolvedMax = options ? lastOption?.value : max; this.handlers.value(resolvedMax, event); - } else if (step !== undefined && max !== undefined && min !== undefined) { + } else { const relativeValue = newLeft / sliderSize; const relativeStep = step / (max - min); const countSteps = Math.round(relativeValue / relativeStep); @@ -164,7 +183,7 @@ class SliderRoot extends Component< handleSlideStep(event: React.KeyboardEvent) { event.preventDefault(); - const { min, max, step = FALLBACK_STEP_VALUE, options } = this.asProps; + const { min, max, step, options } = this.asProps; const direction = event.key === 'ArrowLeft' || event.key === 'ArrowDown' ? -1 : 1; let value = this.getNumericValue() + step * direction; @@ -182,7 +201,7 @@ class SliderRoot extends Component< event.preventDefault(); const { min, options } = this.asProps; - let value: NSSlider.Value = min ?? FALLBACK_MIN_VALUE; + let value: NSSlider.Value = min; if (options) { value = options[0].value; @@ -195,7 +214,7 @@ class SliderRoot extends Component< event.preventDefault(); const { max, options } = this.asProps; - let value: NSSlider.Value = max ?? FALLBACK_MAX_VALUE; + let value: NSSlider.Value = max; if (options) { value = options[options.length - 1].value; @@ -214,18 +233,17 @@ class SliderRoot extends Component< const numericDefault = Number(defaultValue); if (!isNaN(numericDefault)) return numericDefault; - return FALLBACK_MIN_VALUE; + return FALLBACK_VALUE; } const index = options.findIndex((option) => option.value === value); if (index === -1) { const numericDefault = Number(defaultValue); - return isNaN(numericDefault) ? FALLBACK_MIN_VALUE : numericDefault; + return isNaN(numericDefault) ? FALLBACK_VALUE : numericDefault; } - const offset = min ?? FALLBACK_MIN_VALUE; - const result = index + offset; + const result = index + min; if (min !== undefined && index < min) return min; if (max !== undefined && result > max) return max; diff --git a/semcore/slider/src/Slider.type.ts b/semcore/slider/src/Slider.type.ts index ce1a76b3a5..fac85e1db4 100644 --- a/semcore/slider/src/Slider.type.ts +++ b/semcore/slider/src/Slider.type.ts @@ -48,6 +48,14 @@ declare namespace NSSlider { options?: NSSlider.Option[]; }; + type DefaultProps = { + defaultValue: number; + min: number; + max: number; + step: number; + children: React.ReactNode; + }; + namespace Knob { type Component = typeof Box; }