open-wc / context-protocol

A Lit compatible implementation of the context-protocol community protocol
8 stars 1 forks source link

feature: element-based API #25

Open trusktr opened 2 months ago

trusktr commented 2 months ago

For example, I think it would be easier to use context with any custom element system regardless of the authoring experience if users could use elements, because all authoring experiences standardize on HTML.

As an example, solid-element allows writing custom elements with function instead of class, so it is impossible to apply a class mixin to a custom element written with solid-element.

However, providing an element-based API would make context fully agnostic of element authoring patterns.

Here's a code sample. This next piece of code is higher up in the tree in some ancestor custom element:

// Let's assume we're using JSX for templating, but the elements being used as custom elements,
// and we're using a function-based custom element authoring library (f.e. solid-element, enhance, etc):
customElement('higher-up-element', function () {
  return <context-provider name="foo-context" value={anyValue}>
    <any-other-element />
  </context-provider>
})

Now here's the tree inside of any-other-element:

// Let's assume we're using JSX for templating, but the elements being used as custom elements.
// Also for sake of example, this is function-based element f.e. solid-element, enhance, etc
customElement('any-other-element', function () {
  const [fooContext, setFooContext] = createSignal(null)

  createEffect(() => {
    // re-runs any time the context changes because fooContext is reactive
    console.log('foo context', fooContext())
  })

  return <context-consumer name="foo-context" receive={value => setFooContext(value)}>
    <div>context value: {fooContext()}</div>
  </context-provider>
})

As we can see by the example,

React example:

export function MyComponent() {
  // ... use hooks here ...
  const [fooContext, setFooContext] = useState(null)

  return <context-consumer name="foo-context" receive={value => setFooContext(value)}>
    <div>context value: {fooContext}</div>
  </context-provider>
}

etc.

Some custom element libraries may ship with their own set of context to make composition and mixing and matching of their features flexible, and regardless if we're in React/Vue/Svelte/etc we'd want to be able to configure and use that custom element library.

For example, foo-context and any-other-element might both come from a library. A user would be able to configure the elements how they see fit:

// f.e. Preact code:
return <context-provider name="foo-context" value={fooValue}>
  <any-other-element />
  <any-other-element />
  <any-other-element />
</context-provider>

vs

// f.e. Preact code:
return (
  <context-provider name="foo-context" value={fooValue1}>
    <any-other-element />
  </context-provider>
  <context-provider name="foo-context" value={fooValue2}>
    <any-other-element />
  </context-provider>
  <context-provider name="foo-context" value={fooValue3}>
    <any-other-element />
  </context-provider>
)

A custom element library could also abstract it more, as needed, regardless of end user framework:

<!-- f.e. Vue code: -->
<template>
  <foo-context :value="fooValue1">
    <any-other-element />
  </foo-context>
  <foo-context :value="fooValue2">
    <any-other-element />
    <any-other-element />
  </foo-context>
</template>

<script setup>...</script>
trusktr commented 2 months ago

Maybe this is worthy of discussion for the actual context protocol itself, so I opened an issue and linked it to here for more details:

trusktr commented 2 months ago

I just realized too that an element-based API could provide nide type safety in TypeScript-powered templating. All frameworks have the machinery for type definitions of custom elements and their props (typically via JSX types even if the framework's syntax is not JSX). This is nice because a CE lib author can add a JSX type definition for an element such as <foo-context> along with the type for its value prop/attribute, and framework users will have type checking in their type-capable template systems. It requires multiple JSX definitions though, as frameworks have varying shapes for their JSX interfaces.

As an example, I defined the Solid JSX types for Lume's <lume-camera-rig> element here, while the React JSX types are here.