hybridsjs / hybrids

Extraordinary JavaScript UI framework with unique declarative and functional architecture
https://hybrids.js.org
MIT License
3.05k stars 85 forks source link

feat(layout): Add support for layout engine #194

Closed smalluban closed 2 years ago

smalluban commented 2 years ago

Description

A built-in layout system for the template engine. Its main goal is not to replace existing solutions for styling (ex. preferred .css helper), but to add a fast way for the composition of the components in the "view" layouts. The system focuses only on "invisible" CSS rules - display types, position, sizing, alignment, flexbox, grid, etc... The rest "visual" styles should be created in place in the UI components, like colors, fonts, borders, backgrounds... Then those components can be aligned in the views components, by the layout system.

It supports styling elements in the content or render property with the same simple API. It has a strong caching mechanism once the template is compiled, all of the instances share the same CSSStyleSheet instance where it is supported, or use a shared style element attached to the root node of the current subtree.

Usage

The main template attached to the content or render property of the component must have only a single <template> root element with at least one layout rule. Rules added to the root <template> elements apply to the host element (regardless if it uses Shadow DOM or not).

The rules are defined in the layout attribute, with space-separated rules. The attribute can take additional selectors and media queries. The rules can take arguments followed by a colon character:

import { define, html } from "hybrids";

define({
  tag: "my-element",
  content: () => html`
    <template layout="row center" layout@1024px="column">
      <div layout="gap:2">Text</div>
      <div layout="grow">Other text</div>
    </template>
  `,
;

Rules

For now, the following rules apply:

The current rules can be fined in the source code in the src/template/layout.js file.

I plan to add a global function, which will allow adding custom rules, spacing, and breakpoints (but not overwrite defaults).

Mixing

The layout system can be easily mixed with other styling methods, so it is possible to create UI components using layout and then add some "visual" styles using style helpers:

import { define, html } from "hybrids";

define({
  tag: "my-anchor",
  href: "",
  render: ({ href }) => html`
    <template layout="row">
      <a layout="grow" href="${href}"><slot></slot></a>
    </template>
  `.css`
    a { text-decoration: none; }
  `,
};
codesandbox-ci[bot] commented 2 years ago

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit b54406cf651d88b424a235d240d1944a7725bf2d:

Sandbox Source
Hybrids web components playground Configuration
coveralls commented 2 years ago

Coverage Status

Coverage remained the same at 100.0% when pulling b54406cf651d88b424a235d240d1944a7725bf2d on feat-layout into 1310f47dc99f18f75171c08cd924ec8948edead7 on master.

gotjoshua commented 2 years ago

Curious why you chose to reinvent the wheel here? Unocss is so advanced and configurable...

What are your design goals and how to distinguish these new layout features from the amazingness of attributify ?

smalluban commented 2 years ago

@gotjoshua Thanks for your comment and interest in the feature! I always make research before starting building something in hybrids, but I did not find Unocss (thanks for that, I will have a look).

Curious why you chose to reinvent the wheel here?

It may seem like that at first sight. However, some users of hybrids wanted to integrate CSS frameworks, and usually, it is not that simple. Why? The majority of those projects assume, that you are building "sites" (or even apps), but with one central place for your styling - the document. All of the rules are generated to "one" file (or even provided by one compiled file), which you should apply to your page. It fails shortly with Shadow DOM and styles encapsulation. You have to share a whole file with each component, or sometimes it is even not that simple to attach those styles inside of the shadow root.

The main reason behind the layout system is to add a fast and simple way to layout views, not replace the whole traditional approach to CSS in hybrids. Views are usually a unique part of the app - they are not reusable, like UI elements. Without that layout system, you must add a bunch of layout rules into the view component, or create a layout component just for one single usage. My idea is to compose UI elements into the views, which ideally do not have any custom CSS rules. Where you need that control, you create a UI element. Where it is only a non-visual layout (flex, grid, etc) use the layout system.

Besides that technical reasons, which can't be provided by traditional CSS frameworks (I checked, and non of the atomic CSS frameworks easily integrate), there are many other advantages:

Of course, there is much work ahead. It would be great if you like to test it out (use the link from codesandbox comment).

gotjoshua commented 2 years ago

Cool! I'll check it out... Unocss is an engine with very advanced configurable rules and shortcuts.

What i noticed is that it only works out of the box with content (not with the shadow dom from render)

you may want to open an issue with uno about better integration opportunities... Or at least to pick @antfu's and other users brain about what strategies may be possible.

gotjoshua commented 2 years ago

Cool! I'll check it out...

basic test in codesandbox looks good:

https://codesandbox.io/s/hybrids-web-components-playground-layout-experiments-4t4di4?file=/index.html

gotjoshua commented 2 years ago

My idea is to compose UI elements into the views, which ideally do not have any custom CSS rules. Where you need that control, you create a UI element. Where it is only a non-visual layout (flex, grid, etc) use the layout system.

I like this idea, but i think it may end up being too strict to survive in the wild.

I do think that we are aligned on some principles here, as this thought line is why i chose to create

<flex-row>
instead of using
<div class="flex flex-row">

but i often want to extend that basic semantically named layout/ui element with more rules, which unocss attributify allows in very interesting ways:

<flex-row items-end justify-between>
or if you prefer
<flex-row items="end" justify="between">

so, i am curious how your new layout system will play nicely with unocss... and also curious how your design may be influenced if you play around a bit more with unocss!

my components using uno are here: https://gitlab.com/onezoomin/ztax/cdn.zt.ax/-/tree/trunk/apps/color-schemes/components

and the demo is live here: https://f.cdn.zt.ax/apps/color-schemes/ (you need to have a fission user - free and quick to create - but you may need to refresh multiple times as they have some bugs in the login process)

smalluban commented 2 years ago

Rules from the layout system generate one single class with properties, so the following two examples are quite equal in terms of generated CSS:

html`<slot></slot>`.css`:host { display: flex; flex-flow: column; }`
html`<template layout="column"><slot></slot><template>`

The layout system adds a class name to the selector (it is :host(.something)), but from the specificity point of view, if you then add class to your element in the parent template, it should overwrite the properties from the layout system:

html`
  <element-with-layout class="my-class"></element-with-layout>
`.css`
  .my-class { flex-flow: row }
`

However, from what I can see in your components, you are not using content nor render often, so your elements are just to add classes. In that case, it still might be more performant, than using render (which you would have to use to utilize the layout system in your case).

gotjoshua commented 2 years ago

from what I can see in your components, you are not using content nor render often

well... sometimes not, but often i am using content. As i said earlier, i didn't manage to get render working with unocss, so i don't use it at all. I guess that means i'm missing out on the coolness of the shadow dom (which i also admit i haven't fully groked yet).

if you then add class to your element in the parent template, it should overwrite the properties from the layout system

this is troublesome for me. If i want to use the layout system in connection with an atomic framework like unocss, then i need to be able to set additional classes and that the layout sytem will 'play nice'. not sure why additional classes should overwrite, wouldn't it be easy enough to allow extending the generated layout class with additional classes?

smalluban commented 2 years ago

not sure why additional classes should overwrite, wouldn't it be easy enough to allow extending the generated layout class with additional classes?

This is what I meant. It just a class selector with properties, and you can freely extend it with another class, even in template using class proeprty and expressions, it will work together.

gotjoshua commented 2 years ago

Thanks for this exchange @smalluban ! I've been reading more about your vision and getting more on board for the internalized layout engine.

Did you review uno css more? There is one goal of the Uno preset that i think you could totally adopt for the layout engine:

This preset is trying to provide a common superset of the popular utilities-first framework, including Tailwind CSS, Windi CSS, Bootstrap, Tachyons, etc .

For example, both ml-3 (Tailwind), ms-2 (Bootstrap), ma4 (Tachyons), mt-10px (Windi CSS) are valid

This idea that users of any and all of these frame works can all easily adapt to use the engine is very cool.

You can probably even borrow some fancy reg ex's from antfu...