dy / spect

Observable selectors in DOM
https://dy.github.io/spect
MIT License
77 stars 8 forks source link

Direct jsx #16

Closed dy closed 5 years ago

dy commented 5 years ago

With spectators there's a subtle problem. Although it very reminds jquery and at all the idea of lifetime helper is really good, there's no clear way to mount things. Should that be spect from the beginning to mount app? But then for apps that will be used just once - to start the app, and the rest is left to htm, which is way more important.

An idea, reminded by jsxify <document.body> and atomico <host> - putting spect into JSX to directly point to container <mount to=${target|selector} ${...attrs}></mount>. That enables portals, solves problem of passing attrs to host, instantly mounts app to possibly multiple parts of DOM and rids of JS in case of pure jsx.

htm`
<host portal='.dialogs'>
Dialogs portal
</host>
<host>
Root component
</host>
`

Possible approaches:

! <div.class#id></div> for shortcut

dy commented 5 years ago

Can spect signature be merged with jsx? h(target, props, ...children), spect(target, handler, props?).

This way <x ${fn}></x> spect cannot, but aspect (handler) can: spect(target, (target, props, ...children) => {}). This way, aspects are just hyperscript functions, and spect is like "render" from react.

But at the same time, this way <x>${fn}</x> spect can, therefore spect can be an aspect spect(target, () => {}), with special case of passing args if the fn is the second argument (no attributes).

Therefore spect can be used both ways: as htm or directly

html`<.selector>${el => {}}<//>` === spect('.selector', el => {})
html`<div x=1>content</div>` === spect('div', {x: 1}, 'content')

The problem boils down to developing intuition over separating create/read. Eg. <.class - obviously queries elements, <div - makes sure this element exists. But

? how to select all buttons (should we)? . spect('button') !== <button>, therefore we can't spect('button'). ? Can we solve that via context? let el = <button> === let el = spect('button') - creates button. let appEl = <.app><button><//>, selects app and makes sure there's a button inside. <:root button><//> - selects all buttons in host. ? spect('button') - would that query all buttons within the context, or make sure at least one button exists? ? what's the meaning of div.class? Select all div.class or make sure that exists? . If we'd want multiple same aspects on the single node, - we'd have to ignore existing same aspect.

. There's no this problem in passing direct values: <${str|element}><//> === update, <${fn}><//> === create. . We have to allow <.class#id><//> to be complacent with spect(selector, handler) . Effects are like <.ripple class="mdc-ripple">{el => { MDCRipple.attachTo(el) }}<//>. In that, selector-tag can apply props.

? What if we introduce <@.app><//> for read/update? <@div></@>. Makes sense by pronunciation, similar to decorators, as well as makes sense as aspect. Although, spect('@selector') is not really good.

! Having inverse rendering would be helpful. So, aspect internals has access to parent, not children.

. Limiting selectors to . and #:

dy commented 5 years ago

So, final draft:

<#app use=${[Provider, Store, Persistor]}>
  <aside#menu/>
  // maybe prohibiting sugaring is better here to discern selectors visually, afterall it doesn't save tons of time
  <aside id="menu" is=${Aside}/>
  <main is=${Page}/>
  <footer></footer>
<//>
spect('#app', {use: []},
  spect('aside', {id: 'menu', is: Aside}),
  spect('main', {is: Page}),
  spect('footer') 
)

? use-case: tachyons-like ui-framework? nope, does not make sense as no-js. Although extending some fx-styles is good idea, like animations etc.

dy commented 5 years ago

Update, considering token research #23

<#app provider store persistor>
  <aside sidemenu/>
  <main page/>
  <footer />
</>
dy commented 5 years ago

Pre-final noble draft:

$('#app', el => html`
<aside ${sidemenu}/>
<main>${page}</main>
<footer/>
`)