Skip to content

v0.15.0 - write once use anywhere

Latest
Compare
Choose a tag to compare
@trusktr trusktr released this 06 Mar 07:25

What's Changed

  • improve the JSX type helpers, add type helpers and examples helpers for Preact, Vue, Svelte, Stencil.js, and Angular by @trusktr in #35

Details:

feat: Attribute handlers for static observedAttributeHandlers or @attribute now accept new options:

export type AttributeHandler<T = any> = {
  // ...

  /**
   * A side effect to run when the value is set on the JS property. It also
   * runs once with the initial value. Avoid this if you can, and instead use
   * Solid effects. One use case of this is to call addEventListener with
   * event listener values immediately, just like with native `.on*`
   * properties.
   */
  sideEffect?: (instance: Element, prop: string, propValue: T) => void

  /**
   * Whether to convert the property name to dash-case for the attribute name.
   * This option is ignore if the `name` option is set.
   *
   * The default is `true`, where the attribute name will be the same as the
   * property name but dash-cased (and all lower case). For example, `fooBar`
   * becomes `foo-bar` and `foo-bar` stays `foo-bar`.
   *
   * If this is set to `false`, the attribute name will be the same as the
   * property name, but all lowercased (attributes are case insensitive). For
   * example `fooBar` becomes `foobar` and `foo-bar` stays `foo-bar`.
   *
   * Note! Using this option to make a non-standard prop-attribute mapping
   * will result in template type definitions (f.e. in JSX) missing the
   * customized attribute names and will require custom type definition
   * management.
   */
  dashcase?: boolean

  /**
   * The name of the attribute to use. Use of this options bad practice to be
   * avoided, but it may be useful in rare cases.
   *
   * If this is not specified, see `dashcase` for how the attribute name is
   * derived from the property name.
   *
   * Note! Using this option to make a non-standard prop-attribute mapping
   * will result in template type definitions (f.e. in JSX) missing the
   * customized attribute names and will require custom type definition
   * management.
   */
  name?: string

  /**
   * Whether to suppress warnings about the attribute attribute name clashes
   * when not using default `dashcase` and `name` settings. This is
   * discouraged, and should only be used when you know what you're doing,
   * such as overriding a property that has `dashcase` set to `false` or
   * `name` set to the same name as the attribue of another property.
   */
  noWarn?: boolean
}

feat: New on* event handler properties that are similar to native on*

properties such as el.onclick.

When a builtin click event is dispatched, the element's el.onclick() method
is called automatically if it exists. The same can now be achieved with custom
events, so that for example el.dispatchEvent(new Event('myevent')) will also
cause el.onmyevent() to be called if it exists.

Example:

import {Element, element, eventAttribute} from '@lume/element'

// This element will dispatch "foo" events, and `el.onfoo = () => {}` can be
// used for listening to them.
@element('my-el')
export class MyEl extends Element {
  @eventAttribute onfoo = null

  connectedCallback() {
    super.connectedCallback()

    el.dispatchEvent(Object.assign(new FooEvent(), {num: 456}))
  }
}

// A good practice is to make a specific Event subclass for any custom events:
export class FooEvent extends Event {
  num = 123

  constructor() {
    super('foo')
  }
}
import './my-el.js'

const el = new MyEl()

el.onfoo = event => console.log('foo event!', event.num)

document.body.append(el) // logs "foo event! 456"

If not using decorators, define the event attribute with static observedAttributeHandlers:

import {Element, element, attribute} from '@lume/element'

// This element will dispatch "foo" events, and `el.onfoo = () => {}` can be
// used for listening to them.
export const MyEl = element('my-el')(
  class extends Element {
    static observedAttributeHandlers = {onfoo: attribute.event()}
    onfoo = null

    // ...
  },
)

If you're using TypeScript, define the event property with EventListener<EventType>:

import {Element, element, eventAttribute, type EventListener} from '@lume/element'

@element('my-el')
export class MyEl extends Element {
  @eventAttribute onfoo: EventListener<FooEvent> | null = null

  // ...
}

feat: improved JSX type helpers:

  • Type helpers include ElementAttribute (Solid.js JSX), ReactElementAttributes (React and Preact), SvelteElementAttributes, StencilElementAttributes, and VueElementAttributes
  • See examples/kitch-sink-* folders for examples of defining an element with type checking and intellisense in various frameworks.
  • on* event properties (for example onmy-event) can now be specified to
    define event props for JSX. The README has an example, plus the
    framework-types/*.test.tsx files show type test cases for JSX frameworks.
    • Their type will be mapped with string values, as they can accept code
      strings just like native events (f.e. onmy-event="console.log('my-event')".
  • For Solid specifically, all the on* properties are also mapped to on:* JSX
    props with function types.
  • Any boolean JSX props will also accept "true" and "false" string values.
  • Any number JSX props will also accept number strings, f.e. "1.23".
  • For Solid specifically, automatically map prop types for attr:, prop:, and
    bool: prefixed JSX props.
    • attr: props mapped from boolean JS properties will specifically accept
      "true" and "false" strings, or actual booleans (they become strings).
    • attr: props mapped from number JS properties will specifically accept
      number strings, f.e. "1.23", or actual numbers (they become strings).
    • attr:on* props mapped from event JS properties will specifically accept
      any string (it should be code), f.e. "console.log('my-event')".
    • Only boolean JS properties will map to bool: JSX props, and bool:
      will not be available for props of any other type.
      • bool: props will accept only actual booleans, and not the "true" or
        "false" strings (otherwise Solid will set the attribute to always exist
        regardless of the value string values, and this is not an issue with React
        props because React props always map to JS properties never to attributes
        when it comes to Lume Elements).
      • The attr: props will accept only "true" or "false" strings, or actual
        booleans (they get converted to strings).
      • The non-prefixed JSX props, and prop: props, will accept both booleans
        and "true" or "false" strings.
    • Number JS properties are mapped to JSX props that accept numbers as well as
      number strings:
      • The attr: props accept number strings, or actual numbers (they get
        converted to strings).
      • There are no bool: props mapped for number properties.
  • POSSIBLY BREAKING: This update adds to and improves JSX types in
    various ways, which has a chance of being breaking. For example a
    @ts-expect-error comment could start to throw an error, or if you had a
    typed JSX prop like onerous in Solid.js for an element whose JSX types you
    defined with ElementAttributes, there might be a type error because the type
    definition will expect a function that handles an Event (named "erous"), but this scenario is unlikely. To migrate, use attr: or prop:
    prefixes to be explicit, for example prop:onerous={notAnEventHandler}. If
    any issues, please find our community chat or forum at https://lume.io and
    reach out!

Full Changelog: v0.14.0...v0.15.0