Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support for React, Vue, Svelte, Solid, Angular, etc, by making Custom Elements #604

Open
trusktr opened this issue Feb 23, 2024 · 0 comments

Comments

@trusktr
Copy link

trusktr commented Feb 23, 2024

Hello! This lib is awesome! I've been wanting something like this, but in the form of Custom Elements, so that it can be supported no matter what project I'm working in, while staying in declarative-reactive paradigms of the current app.

Would you be interested in Tweakpan elements?

For example, based on the Number example, It could look like the following in an HTML file:

<tweak-pane id="pane">

  <!-- This one has only the number input (not a range) -->
  <tweak-number name="speed" value="0.5"></tweak-number>

  <!-- This one also has the draggable slider, because it is a range. -->
  <tweak-number name="count" value="10" min="0" max="100"></tweak-number>

</tweak-pane>

<script>
  const pane = document.getElementById('pane')
  
  // pane.params could be the objects that contains all the values (besides also being able to read the DOM element values directly)
  console.log(pane.params)

  // provide a custom params object (random idea, we could chat about how we want it to work):
  pane.params = {...}
</script>

For simplicity, someone might want to integrate into HTML (or React/Vue/Solid/etc) by using only the root element, but letting it create the pane without specifying the child elements:

HTML:

<tweak-pane id="pane"></tweak-pane>
<script>
  pane.params = {...}
  pane.autoBind = true // creates the UI automatically
</script>

Solid.js (and similar, f.e. JSX, React, Preact, etc):

function MyApp() {
  const [params, setParams] = createSignal({...})
  return <tweak-pane params={params()} autoBind={true}></tweak-pane>
}

Lit (with html template tag):

class MyEl extends LitElement {
  params = {...}
  render() {
    return html`<tweak-pane .params=${this.params} autobind></tweak-pane>`
  }
}

etc, etc, etc.

By making a set of Custom Elements, we can automatically support all declarative-reactive frameworks.

TweakPane is already written as plain JS components, so what we would do is wrap the API in custom elements.

For example, here's what it would start to look like using Lume Element (my lib for defining Custom Elements more simply), but you can also imagine using another Custom Element lib like Lit:

import {element, Element, attribute} from '@lume/element'
import {createEffect, onCleanup} from 'solid-js' // (Lume Element is built over reactivity and templating from Solid.js)

@element('tweak-pane')
class TweakPane extends Element {
  #params = {}

  @attribute
  get params() {
    return this.#params
  }
  set params(val) {
    if (typeof val === 'string') this.#params = JSON.parse(val) // string values can come from HTML attributes
    else this.#params = val
  }
  
  @booleanAttribute autoBind = false // f.e. `<tweak-pane auto-bind />`
  
  #pane = new Pane()

  connectedCallback() {
    super.connectedCallback()
    
    createEffect(() => {
      // Any time `this.params` or `this.autoBind` change (f.e. because the user set a new values via JS property or HTML attribute) this "effect" will re-run because Lume Element attribute properties are reactive (Solid signals).

      if (!this.autoBind) return

      for (const key of Object.keys(this.params)) {
        this.#pane.addBinding(this.params, key)
      }

      // onCleanup runs any time the effect will re-run, to clean anything up from the last run.
      // This is the simplest/naive implementation, any time `this.params` changes, just make a whole new Pane with new params.
      // In a more advanced implementation, maybe we diff params, change only the parts of the pane that are added/removed.
      onCleanup(() => {
        this.#pane.dispose()
        this.#pane = new Pane()
      })
    })
    
    
    // ... etc ...
  }

  // ... etc ...
}

Furthermore, we can provide TypeScript types for JSX type checking in Solid, React, etc. For example, here's how I hook Lume 3D elements into JSX and DOM type defs:

https://github.com/lume/lume/blob/a5ea06101174ed3f8fc165b82a76d1521ae7f5ce/src/cameras/CameraRig.ts#L425-L437

and React users can import the React type:

https://github.com/lume/lume/blob/a5ea06101174ed3f8fc165b82a76d1521ae7f5ce/src/cameras/CameraRig.react-jsx.d.ts

We can similarly hook up type defs for other systems that also rely on JSX (Svelte and Vue use JSX type defs for their templating even though the syntax is not JSX).

Basically we can write components once, run within any declarative-reactive framework, and the only framework-specific thing we need to do is provide the type hookup for TS users.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant