Closed dy closed 5 years ago
Tachyons is a set of standard mini-classes. You don't invent a classname to apply tachyon, you just apply it. Same way spect could have a set of mods - known components/behavior particles, like draggable, tooltip etc. Spectacle? <div class="@draggable @tooltip"...>
. But in this case we need padding params. Instead, would be easier to have attribute <div class="...aspects ...tachyons" draggable=${params} />
, which is unlike tachyons - they define aspect+param+value at the same time: <div class="drag-speed-2 mt-3"></div>
, so - denormalized cached often-used particles.
Such particles are registered somewhere and take signature. .mt-${param}: { behavior }
. What construct best describes such particles?
{
[`pfx-${param}`]: (el, param) => {}
}
x`
pfx-${param}: ${ (el, param) => {} }
`
function pfx (param) {
return (el) => {}
}
h`
<.pfx-${param}>
${}
<//>
`
Vue, express, react-router: https://github.com/pillarjs/path-to-regexp
{
'.m:side-:size': (el, side, size) => {}
}
'.m%w-%d': (el, side, size) => {}
'.m{w}-{size}': (el, side, size) => {]
An identifier, set of params and handler.
<div data-x="" >
<div class="x-width:2-side:l" >
<div x="...">
<div x-param="">
<div class="element">
<div class="@element">
<div element>
<div aspect-element>
<div data-element>
title
, hidden
, autocapitalize
, contenteditable
, draggable
etc (https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/translate).<div is="element">
<div use="element">
is
or use
is possibly slower than classname, although there's hidden
attribute example.is
and use
is a partial-case of 2.<element>
$`<some-el>` - usage.
$('some-el') - definition.
Lol. Plain strings are for creation. Template literals are for usage. But - how do we employ htm to do that?
let html = h.bind($) - turns everything into definition mode, instead of usage.
Then we can use dream solution - create method.
// that doesn't start observing.
let aspect = $.create('#app, .app', fn)
// this triggers observing
$('#app', aspectFunction)
But then that's redundancy. We could run as
$(aspect)
// or
$`<${aspect}/>`
which would be a component-compatible invocation. But that would break classical notation $(selector, function)
.
We could make let aspect = $(selector, fn)
an anonymous aspect, instantly runnable, and let aspect = $.create(selector, fn); $(aspect)
for registered aspects. But that's redundancy of signatures, no clear difference $.create
and $
.
All that is just to separate $('div')
as registering selector-aspect from $('div')
as creating html structure, or $('custom-el')
as an element-aspect from $('custom-el')
as creation.
If we remove registering from $
completely, prohibiting constructs like $('#app', ...rest)
, we complicate binding h
to real DOM:
let app = $.create('#app', el => {
})
$`<div id="#app"></div>`
In fact though that makes html really clean, simple and valid, without weird connections like <#app><//>
. Although that conceptually breaks portals $`<${document.body}><//>`
, also makes insertions impossible: $`<${aspect}>...<//>`
.
We could remove html logic from spect completely, to htm
and soft-h
functions.
We could identify first-level tokens as binding, the rest as hyperscript:
$`
<${binding}>
${ html creation code, inc. aspects }
<//>
`
But then first-level code would never be able to create hyperscript, and not-first-level code would never be able to register listeners:
// this attaches nested div element to all div elements (which is confusing - <el> is always expected to be a tangible real element, not fake binding!)
$`
<div>
<div></div>
</div>
`
// this makes no sense (indeed selectors within selectors - what's the use-case? Although - portals do that, and any side effects)
$`
<${document.element}>
<#app>${123}<//>
<//>
`
What if we'd separate tpl from binding as
$('div')`htm`
$(el)`htm`
// that's the same as
$('div', htm)
That still seems to be two separate functions - creation h
and observing $
.
What if we make mounting an aspect too? <${target}><//>
is actually a type of "portal" aspect (host, root, mount), and should be handled by the same rules:
h`
<div mount=${document.body}>...</div>
`
// then the aspect is
$('[portal]', el => {
let target = attr('portal')
target.appendChild(el)
})
h
from the aspect-binding inclusions.
-> we have 2 separate functions: registering aspects $
and soft-ensuring html
, which is standard htm with some hyperscript function.$
and html
are separate functions - one is aspect observer, the other is html reconstructorhtml
works the way similar to hooks: it makes sure provided order of HTML elements, defined by current aspect is guaranteed; it doesn't ensure exact and only HTML structure.html
generally doesn't take selectors as tag elements <#id><//>
. As an exception it may take real HTML instances. See next.portal
or other custom aspect is used to mount html elements to specific taget.$
function can be used to define context of hooks as $(target, hook)
, eg. let {v} = $(target, attr('a'))
attr(name, target?)
? at(target)(attr('name'))
? at(target, attr, 'name')
? attr.call(target, ...args)
? at(target, el => attr('name'))
? $(target, el => attr('name'))
?init
fx can be used to invoke constructor/destructor hooks. They're triggered when aspect token is assigned/removed, unlike the mount/unmount
hooks.$('custom-el', el => {})
, $('[is=custom-el]', el => {})
.`<div is=${header}><//>` // h('div', {is: header})
is
selectable by aspect, but difficult to implement $('[is]', el => { ??? })
- the attr is serialized function.
`<div>${header}<//>` // h('div', header)
? isn't that weird that we define aspect as a child of node?
Is child an aspect? Maybe, but not every aspect is a child.
If we'd called lib mod
, children wouldn't be mods.
`<${header}><//>` // h(header)
What element should be passed to aspect? Some undefined one? Like custom elements.
`<div id=${header}></div>`
Confusing! Why function stands as id?
Also that's custom handling of ids.
That is the problem caused by allowing aspect to be any selector instead of strictly defined one.
<${aspect}/>
<div ${aspect}></div>
(not compatible with hscript, needs modifying htm)<div class=${aspect}</div>
<div is=${aspect}></div>
For now the only way to connect aspects - is via loose identifying.
`<div#id></div>`
Splitting to $
and h
introduces that loose contract and prohibits use of functional insertions, instantly registered as aspects, because that makes h
quirky aspect-cases enabled.
Actually, custom elements is quite legal way to attach aspects:
$('#header', function header (el) {...})
h`<header id="header"></header>`
//reduce boilerplate iteration 1
$('header', header)
h`<header/>`
//reduce boilerplate iteration 2
h`<${header}/>`
wrong. That undermines the principle of grounded html, when the user can be grounded with generated code. Possibly we should prohibit components insertion.
Since aspects observe real DOM, they can't know if custom attribute, like function, was inserted.
? h
could possibly take handle of notifying aspects about creation.
function highlight (el) {}
// registers highlight as random string, sets observing that string
h`<a ${highlight}></a>`
But how'd we pass custom non-stringy props to that aspect? No way, right? Because aspects are not elements - therefore they can't take props passed to them, they can only read whatever's assigned to element, or accept own propset in some way. Angular registers inputs for that purpose, we use hooks. But htm renders attributes, so we read attrs via hooks, whereas react and angular read props. So our htm would render props, which would not be a direct hyperscript. So basically we insert hooks into our hyperscript function to enable aspects take non-stringy props.
(! mb abandon custom h
implementation for standard one and focus on aspects. Eg. react could easily be integrated via refs as <a ref=${aspect}></a>
)
. that's a bit confusing as well to have aspects registered as direct elements, not at the point of registering $('header')
- it's all clear here, but at the point of declaring <header></header>
- that's completely unobvious that the standard element will be augmented with aspect. Even with css that's confusing practice and should be left to narrow normalize/polyfill set.
. since on
has signature of selecting target, mb that's meaningful to describe aspects aside from the main html code, and in main html code just put markers.
$(document.body, () => {
$('#app', el => { ...init })
$('[draggable]', el => { ... init draggable })
$('[storage]', el => { ... connect storage })
$('[tooltip]', el => {... connect tooltip })
html`
<div id="app" class="mr1 ml2" draggable tooltip="abc" storage="abc">
</div>
`
})
Stuffing that code into html
would make it a mess.
But on the other hand that creates bindings boilerplate like
$('#app', () => {
$('[sidebar]', aside)
$('[header]', header)
html`<aside sidebar/><header header/>`
})
function aside () {}
function header () {}
// that could've been minimally done as
// that is more expressive and way easier to get started, similar hscript on steroids
$`<#app><${aside}/><${header}/><//>`
// which is
$('#app', html`<${aside}/><${header}/>`)
// which is with html complacency ground is
$('#app', html`<aside app-sidebar/><header app-topbar/>`)
// which is redundant description, since sidebar/topbar functions already may have names
// react-way aspects would be
<aside Aspect/><header Topbar/>
// htm-way aspects are unavoidably
h`<aside ${sidebar}/><header ${Topbar}/>`
// or if aspects are registered
h`<aside sidebar/><header topbar/>`
?! <div@header@mutable/>
? - messy strings
?! html-aspect modifier html`<${document.body}><div#app></div><//>`
? - same as mount
aspect.
So very expressive way of aspects is
$`
<#app>
<header is=${topbar}><//>
<aside is=${sidebar}><//>
<main path=${
{
'/': home,
'/signIn': '/sign-in',
'/singin': '/sign-in',
'/sign-in': () => import('./sign-in.js'),
'/dashboard': () => import('./dashboard.js'),
'/catalog': () => import('./catalog.js'),
'/users/:userId': (el, {userId}) =>
}
}/>
</#app>
`
So, aspects provide ground for mods
, which are a set of special attributes, allowing really graceful h-script almost without js. Such mods as: target
, is
, route
etc - all of them provide token and take config.
! observation: htm is so cool - it takes attributes for no-tag fragment-ish elements, which can possibly take arguments:
`<mount=${target}></>`
// which is the same as fragment with key
< mount=${target}></>
// which makes aspect === initial element, or direct/main aspect (suuuper cool)
<div mount=${target}></div>
<mount=${target}></div>
// ā these are not exactly different, because fragment is mounted to target, same as div element
// also that makes registering direct aspect meaningful:
$('div') => `<div>` - in sense of create that aspect.
That enables natural separation of creation/deletion:
h(tagname?, {container?, ...aspects}, children)
- natural flow: what, how, what's next.
And registering aspects is just an external tool.
We don't need selectors to register aspects. We can just directly do $.create('aspect', handler)
, and it will be processed one possible way only, to select as <div aspect/>
, not id/class/etc.
Aspectful HTML logic explained.
<div></> - fragment with single main `div` aspect
<div=${options}/> - fragment with single main aspect with options
< div=${options}></> - fragment with single secondary aspect with options
<div>${children}</> - fragment with `div` and `children` aspects, same as <div children=${children}/>
<children=${children}/> - fragment with single main children aspect, same as <>${children}</>
Insertion:
< ${aspect}></> - dynamically inserted secondary aspect
<${aspect}></> - dynamically inserted primary aspect
<>${aspect}</> - fragment with children aspect options, same as < children=${aspet}/>
<${aspect}=${options}/> - dynamically inserted aspect with dynamic options
Since tree logic is an aspect too, with reserved children
name, there are consequences:
<parent=${aspect}></>, <div parent=${}></> - for ensuring parent container.
<parent=.selector>${el=> {}}</> - is the same as <parent=.selector children=${el=> {}}/>
Insertion function logic:
<${el => {}}></> - el is fragment here
< ${el => {}}></> - impossible with htm, needs forking, el is fragment
<x=${() => {}}></> - fn is just aspect options, no result
<>${el => {}}</> - < children=${el => {}}/>
React has children
prop always passed to props object. Therefore the pattern is passing all aspects as props, not the main aspect.
<${ ({element, children}) => {} }/>
<>{$({element, children}) => {}}</>
But - children
is already a property of element
, that's duplication; also, the rest aspects are almost always present as props on element
directly. Also, there's no standard element
aspect, so that makes a contradiction. Finally, it's just inconvenient syntax. So, the insertion pattern is the following:
<${el => {}}></>
<>${el => {}}</> === < childNodes=${...}></>
So, every standard aspect is just a prop of element {aspect1, aspect2: options2, ...aspects} === <aspect1 aspect2=${...} ...aspects/>
. This way, mounting is <parentElement=${}></>
or a custom aspect.
! functional aspects: <appendChild(${args})>
! xslt inspiration: https://en.wikipedia.org/wiki/XSLT_elements
Eg. xslt defines an aspect namespace: <${document.body}></>
, we can use that as any other aspect: <div ${document.body}></>
.
Actually, we can think of having main aspect reserved for DOM-related reality. Eg. if we have non-DOM, but some other reality, the main aspect there would be something different. Like sound, or syntax trees.
<{main-reality-aspect} {other-realities-aspects}>{sub-aspects}</>
So, such direct aspects are anonymous <${fn1} ${fn2}></>
- they apply some action on created container. If we pass something other than a function - we consider that object an aspect, like class or other. Passing the other aspect in place of main aspect <${document.body}/>
acts like an alias, or <augment=${document.body}></augment>
- so we describe additional aspects to body
.
Precedents of using refs in html:
<div ref={el => {}}></div>
- in react<use href="#element"></use>
- in svg, following <defs>
<link rel=xxx href="URL" />
<portal src="url"/>
<label for="id"></label>
- in forms<x slot="name"></x>
- puts element into template slot element by name, following <template>
<Dropdown anchor=${el}/>
- in material-ui<td headers="id1 id2 id3">
- refers headers for a table cell https://www.w3.org/TR/html4/struct/tables.html#h-11.4.1<input list="datalist-id">
To extend every element in DOM, matching some selector, we should provide some special aspect:
<š=".class"></>
. But since we reduce selectors to attributes only, we can pick simple slot-like property:
<aspect="app">Ensure content ${el => `and functionality`}</aspect>
<div app/>
// which is the same as
$('[app]', el => h`Ensure content`)
So which form is better:
<aspect=app>...</>
<aspect name="app"></>
<aspect selector="[app]"></> +custom-element-able
<$="[app]"> +selector +spect +jquery +theoretically pure -bad attr name -incompat with html
<div aspect="app other"></div>
<div app other></div>
Notes:
- <children="#other-el"></> - inserted children are taken as other element by selector/URL, as SVG href. Not the same as <>#other-el</>, so children can't have reference, that is direct value. Actually, `children` is not assignable prop.
- <use href="URL"></use> - meaningful primary aspect, could be interesting to mix it to other aspects.
- <div span/> - primary aspects aren't always mixable. Although would make sense sometimes <nav ul><li a></></>
- <children></> - refers to nested nodes (not in HTML though, in DOM), therefore <><parentNode></> - refers to outer node (not in HTML though, in DOM). These props are weird aspects:
- <parentNode=${el => el.parentNode}/> - that rewrites parent, so that isn't exactly the real DOM children, that's an overriding alias aspect, same as <x children=${el => el.childNodes}>abc</> -
that can override `abc` children, so - duplicate declaration.
So possibly we could mark such technical aspects somehow (actually they're both read-only).
- <! is used for aspects from another reality - <!doctype, <![CDATA[, <!-- comment.
- <main><path/></> is the same as <main path/> - aspects propagate up, being defied on shallow document fragments.
- <x ${fn}> - anonymous aspects cannot take properties.
- <${el} ${el}></> - makes one element a prolongation of another. There are some rules to aspect combinations.
If we pick <aspect>
custom element, it forwards us to #25.
What if we make :parent
a pseudo, similar to :is
/:where
.
:root
selector already https://developer.mozilla.org/en-US/docs/Web/CSS/:targetSeems that having aspects registered via custom-element is not very profitable strategy - not much we can do by just describing it in pure html - just registering them is not expressive and doesn't give much profit. Instead, we can focus on registering aspects and hyperspect engine, providing h function for aspect-oriented HTML.
It's sad though that the only thing that separates $ from h is this inconsistency of first selector.
$('#app', el => h`
<${el}>
<header ${topbar} title="App title"></>
<aside ${sidebar}></>
<main ${el => el.path = history.location}/>
</>
`)
//same as
$`<!app>
<header ${topbar} title="App title"></>
<aside ${sidebar}></>
<main ${el => el.path = history.location}/>
</>`
Similar to passing another aspect/target to provide aspect $`<${el}></>`
(that is a special type of anonymous aspect), we could whether pass some special object $`<${sel()}></>`
or just special type of string (selector) <#app></>
, or have some token <@app></>
, or explicit form <a-spect selector=${}>
or etc. That doesn't save from the situation though when we need to deal with html:
$`<#app>
${el => h`<${el}>...</>`}
</>`
So, h
function must be an effect, similar to on
. With on
we can:
on('click', () => {}) // registers for current element
on(el, 'click', () => {}) // same
on('.class', 'click', () => {}) // more generic registerer
html('.class', `...`) // make html content for an element.
Finalizing.
There's no $ function, there's a set of effects, html
is one of them.
The effects by default take context of outer effect, unless selector is explicitly defined.
html`content` - put directly to body
html('#el', `content`) - put into element
html('#el', `<div>${ html`content` }</>` - put into `div` element
That is similar to plain html. If you define some html code, it's placed directly into the container element.
Maybe it's easier to split up $
and html
/fx
for clarity. $ defines target context, effects apply contextual actions. on(selector?, event, handler)
is not very efficient with that signature - doesn't look like delegate.
$(selector, el => {
on('click', 'em', handler) // delegates to local em
// or on('click', handler, [delegate])?
on('click', handler) // attaches to element directly
html`Ensure <em>contents</em> <...> - works as html hook/reducer`
// scopes to another target, attaches initial handler
$(document.body, el => on('click', handler))
$('#side-area', el => html`<...>Additional side-effect html`)
})
That +keeps effects clean +keeps context setter clean +keeps spect signature +makes spect-less effects effectless +adds main exports
Yep. A way to go. Direct HTML can be achieved as $(document.body, el => html``)
.
The problem of aspects is that they aren't good as primary elements, they perfectly augment some ready object. That makes them a perfect addition to
h
function, but not theh
itself.What are the possible approaches to organize aspect-enabled components?
1. Class/Id tokens
<div.date-range class="date-range" ...>
. id/class aspect difference is uniqueness of aspect on the page<#portal><div#aspect/><//>
2. Direct component
3.
is
,use
attributesis
.4. Custom elements, least-nonstandard
? What's the way to register multiple aspects? Possibly least-nonstandard classes?
5.
@
attribute@
meaning@spect
.@
is not valid attribute name, no native html support6.
@
-prefixed classes<@
is not valid tag name ~ possibly have to register all aspects by token name7. Generated classes
use
, since it's clear that this is a string@
- noise ~ custom elements are kept aside, possibly a target case.
and#
selectors$
inh
corresponds to$
outside. ~@
can be used as generated classes prefix.8. Disallow $ function, make h main entry as
<@>
tag is treated as aspect, anything used via@
class applies aspect@
is not allowed html element name<@Grid
. We're off custom elements and components.@
class is actually<.@TargetName
aspect by classes.9. Attributes