wix / stylable

Stylable - CSS for components
https://stylable.io
MIT License
1.27k stars 62 forks source link

Union Types planning #1984

Open tomrav opened 3 years ago

tomrav commented 3 years ago
barak007 commented 2 years ago

(WIP)

Why?

We are already encounter inline combinations of types in rules:

/* compound selector with multiple types */
.x.y {
}
/* nested union types */
:is(.x, .y) {
}
/* nested intersection types */
:is(.x.y) {
}

When looking at definition

Today when we define parts in Stylable we can use -st-extends to give it a "single" type. We want to allow multiple types to be assigned to the same part

Tasks:

Cases:

Union

Native elements:

/* @dev-check .root:not(div, span) */
.root {
  -st-extends: div | span;
}
function Comp({ isBlock }) {
  return isBlock ? (
    <div className={classes.root} />
  ) : (
    <span className={classes.root} />
  );
}

Element and class:

.full-width {
  /* implicit -st-extends: Element */
}

/* @js-export entry__root */
/* @dev-check .root:not(header, .full-width) */
.root {
  -st-extends: header | .full-width;
}
function Comp() {
  <header className={st(classes.root)} />
  <header className={st(classes.root, classes['full-width'])} />
  <div className={st(classes.root, classes['full-width'])} />
}

Two classes with non conflicting states:

/* entry */
.full-width {
  -st-states: size(enum(viewport, content));
}

.flex {
  -st-states: orientation(enum(horizontal, vertical));
}

/* @js-export entry__root */
/* @dev-check .root:not(.flex, .full-width) */
.root {
  -st-extends: .flex | .full-width;
  /* :is(.flex, .full-width) */
}

.root:size(viewport) {
}
.root:orientation(horizontal) {
}
.root:size(viewport):orientation(horizontal) {
}
function Comp() {
  // consider add dev check for this  
  <div className={st(classes.root, {orientation: '...'}, classes['full-width'])} />
  // skip the example
}

Conflicting states:

/* entry */

.flex-center {
  -st-states: orientation(enum(h, v));
  display: flex;
  align-items: center;
  justify-content: center;
}

.flex {
  -st-states: orientation(enum(horizontal, vertical));
  display: flex;
}

/* @js-export entry__top */
.top {
  -st-extends: .flex | .flex-center;
  /* :is(.flex, .full-width) */
}

/* @transform-error Argument of type 'orientation(horizontal)' is not assignable to parameter of type orientation(enum(h, v)). */
.top:orientation(horizontal) {
}
/* utils */
.flex-center {
  -st-states: orientation("[data-orientation][data-xxx]");
  display: flex;
  align-items: center;
  justify-content: center;
}

/* entry */
.flex {
  -st-states: orientation(enum(horizontal, vertical));
  display: flex;
}

/* @js-export entry__top */
.top {
  -st-extends: .flex | .flex-center;
  /* :is(.flex, .full-width) */
}

/* @rule .entry__top:is(.entry--orientation-horizontal, [data-orientation][data-xxx]) */
/* @rule .entry__top.entry--orientation-horizontal, .entry__top[data-orientation][data-xxx] */
/* @rule .entry__top.entry__top:where(.entry--orientation-horizontal, [data-orientation][data-xxx]) */
.top:orientation(horizontal) {
}

/* we need to find solution for this case */
:orientation(horizontal) {
}

/* should only work if there is a default state value  */
.top:orientation {
}

Intersection:

Native elements intersection:

/* entry */
.root {
  /* @analyze-error cannot intersect two element */
  -st-extends: div & span;
}

.full-width {
  /* implicit -st-extends: Element */
}

/* @js-export entry__top entry__full-width */
.top {
  -st-extends: header & .full-width;
  /* header.full-width */
}

Parts intersection:

/* A */
@st-import [same] from './common.st.css';
.root {
}
.same {
}
.conflict {
}
.a-only {
}

/* B */
@st-import [same] from './common.st.css';
.root {
}
.same {
}
.conflict {
}

/* entry */
.root {
  -st-extends: A & B;
}
/* @rule .entry__root .common__same */
.root::same {
}

/* @transform-error .conflict in A & B does reference the same part */
.root::conflict {
}

/* ? WHAT TO DO WHEN ONLY ONE SIDE DEFINE THE PART ? */
.root::a-only {
}