hybridsjs / hybrids

Extraordinary JavaScript UI framework with unique declarative and functional architecture
https://hybrids.js.org
MIT License
3.03k stars 85 forks source link

Add a helper for assigning component properties #242

Closed Qsppl closed 5 months ago

Qsppl commented 5 months ago
          This "was" a limitation of the TS itself, that the setter must accept the same type as a getter. I am sure, it was like this when I created definitions for TS.

          Your type says:
          ```
          model: IModel
          ```

          But you are trying to set it to a `number`. As this is expected in hybrids, I am not sure if this is possible to define in TS.

          EDIT: You can try with `mode: IModel | number`, but then you are opening many conditions when using that property, as TS claims that it might be also a `number` (which getting the value is not correct).

Originally posted by @smalluban in https://github.com/hybridsjs/hybrids/issues/237#issuecomment-2009503303

I wrote a helper function to assign values ​​to component properties without typing errors:

type AllowedValueType<PropertyType> = PropertyType extends { id: any }
    ? PropertyType | PropertyType['id'] | undefined
    : PropertyType

export default function setProperty<
    Element extends HTMLElement,
    Property extends keyof Omit<Element, keyof HTMLElement | "render" | "content">,
    Value extends AllowedValueType<Element[Property]>,
>(element: Element, property: Property, value: Value) {
    /// @ts-ignore
    element[property] = value
}
// #################################### TESTS ####################################

interface IModel {
    id: number
    content: string
}

interface IComponent extends HTMLElement {
    modelId?: number
    model: undefined | IModel
    draft: IModel
    edit: boolean
    hold: boolean
}

function setModel(host: IComponent) {
    setProperty(host, 'model', 2)
    setProperty(host, 'model', undefined)

    /// @ts-expect-error
    setProperty(host, 'model')
    /// @ts-expect-error
    setProperty(host, 'model', "1")
    /// @ts-expect-error
    setProperty(host, 'model', () => 1)
    /// @ts-expect-error
    setProperty(host, 'model', { a: 1 })
}

function setDraft(host: IComponent) {
    setProperty(host, "draft", host.model)
    setProperty(host, "draft", 3)
    setProperty(host, "draft", undefined)

    /// @ts-expect-error
    setProperty(host, 'draft')
    /// @ts-expect-error
    setProperty(host, "draft", "1")
    /// @ts-expect-error
    setProperty(host, "draft", () => 1)
    /// @ts-expect-error
    setProperty(host, "draft", { a: 1 })
}

function toggleEdit(host: IComponent) {
    setProperty(host, "edit", true)
    setProperty(host, "edit", false)

    /// @ts-expect-error
    setProperty(host, 'edit')
    /// @ts-expect-error
    setProperty(host, 'edit', "1")
    /// @ts-expect-error
    setProperty(host, 'edit', () => 1)
    /// @ts-expect-error
    setProperty(host, 'edit', { a: 1 })
}

Could you embed it somewhere in the library?

Qsppl commented 5 months ago

Perhaps, instead of a universal function setProperty, we should use special setModelId for the case when a property with a model descriptor is assigned ModelIdentifier and clearModel for the case when the same property needs to be assigned the value undefined.

smalluban commented 5 months ago

As this might work, I am not a fan of adding a function to just make an assertion because an optional language has some limitations in definitions. I am waiting for a nicer solution on the TS side.

Qsppl commented 5 months ago

Okay, I'll write them such a proposal on GitHub with examples from other typed languages ​​where there is no such restriction. I'll let you know what they say.