2021.9.25 • Published 3 years ago

@amory/style v2021.9.25

Weekly downloads
24
License
Apache-2.0
Repository
github
Last release
3 years ago
  • @amory/style :properties: :header-args: :cache yes :comments no :mkdirp yes :padline yes :results silent :end: #+startup: showall nohideblocks hidestars indent

** Table of Contents :TOC:

** Usage

*** With Next.js

**** pages/_app.js

#+begin_src js import { getStyles } from '@amory/style' import App, { Container } from 'next/app' import React from 'react'

export default class extends App { componentDidMount () { / For testing purposes during development / window.getStyles = getStyles }

render () { const { Component, pageProps } = this.props

return (
  <Container>
    <Component {... pageProps } />
  </Container>
)

} } #+end_src

**** pages/_document.js

#+begin_src js import { getStyles } from "@amory/style" import Document from "next/document" import React from "react"

export default class extends Document { static getInitialProps ({ renderPage }) { const initialProps = renderPage ( (App) => (props) => (<App {...props} />) )

return {
  ... initialProps,
  "styles": (
    <React.Fragment>
      {initialProps.styles}
      <style
        dangerouslySetInnerHTML={{ __html: getStyles () }}
        data-creator="@amory/style" />
    </React.Fragment>
  )
}

} } #+end_src

** License

#+begin_quote Copyright 2019 [https://github.com/ptb]

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. #+end_quote

** Source

*** API

**** create

#+HTML: #+begin_src js :tangle src/api/create.js import { css, toPairs } from "./index.js"

export function create (params = {}) { return toPairs (params).reduce (function (styles, style) { const property = style0 const value = style1

styles[property] = css (value)

return styles

}, {}) } #+end_src #+HTML:

#+HTML: #+begin_src js :tangle src/api/create.test.js import ava from "ava" import { create } from "./create.js" import { css } from "./css.js"

ava ("given undefined arguments", (t) => { const actual = create () const expect = {}

t.deepEqual (actual, expect) })

ava ("given an object with simple declarations", (t) => { const actual = create ({ "banner": { "display": "block", "width": "80%" }, "product": { "color": "#f00", "display": "block", "flex", "grid" } })

const expect = { "banner": "drtx9 dtndl", "product": "jk2a9 dr4gk" }

t.deepEqual (actual, expect) })

/ eslint-disable max-lines-per-function / ava ("given an object with multiple 'fontFamily' declarations", (t) => { const fonts = { "avenir300": { "fontFamily": { "fontFamily": "Avenir", "fontStyle": "normal", "fontWeight": 300, "src": "url(/fonts/avenir-300-light-normal.woff) format(woff)" } }, "avenir300i": { "fontFamily": { "fontFamily": "Avenir", "fontStyle": "italic", "fontWeight": 300, "src": "url(/fonts/avenir-300-light-oblique.woff) format(woff)" } }, "avenir400": { "fontFamily": { "fontFamily": "Avenir", "fontStyle": "normal", "fontWeight": 400, "src": "url(/fonts/avenir-400-book-normal.woff) format(woff)" } }, "avenir400i": { "fontFamily": { "fontFamily": "Avenir", "fontStyle": "italic", "fontWeight": 400, "src": "url(/fonts/avenir-400-book-oblique.woff) format(woff)" } }, "avenir500": { "fontFamily": { "fontFamily": "Avenir", "fontStyle": "normal", "fontWeight": 500, "src": "url(/fonts/avenir-500-roman-normal.woff) format(woff)" } }, "avenir500i": { "fontFamily": { "fontFamily": "Avenir", "fontStyle": "italic", "fontWeight": 500, "src": "url(/fonts/avenir-500-roman-oblique.woff) format(woff)" } }, "avenir600": { "fontFamily": { "fontFamily": "Avenir", "fontStyle": "normal", "fontWeight": 600, "src": "url(/fonts/avenir-600-medium-normal.woff) format(woff)" } }, "avenir600i": { "fontFamily": { "fontFamily": "Avenir", "fontStyle": "italic", "fontWeight": 600, "src": "url(/fonts/avenir-600-medium-oblique.woff) format(woff)" } }, "avenir700": { "fontFamily": { "fontFamily": "Avenir", "fontStyle": "normal", "fontWeight": 700, "src": "url(/fonts/avenir-700-heavy-normal.woff) format(woff)" } }, "avenir700i": { "fontFamily": { "fontFamily": "Avenir", "fontStyle": "italic", "fontWeight": 700, "src": "url(/fonts/avenir-700-heavy-oblique.woff) format(woff)" } }, "avenir800": { "fontFamily": { "fontFamily": "Avenir", "fontStyle": "normal", "fontWeight": 800, "src": "url(/fonts/avenir-800-black-normal.woff) format(woff)" } }, "avenir800i": { "fontFamily": { "fontFamily": "Avenir", "fontStyle": "italic", "fontWeight": 800, "src": "url(/fonts/avenir-800-black-oblique.woff) format(woff)" } } }

const actual1 = create (fonts)

const expect1 = { "avenir300": "c5w4b", "avenir300i": "c59u0", "avenir400": "c5krw", "avenir400i": "c5c5b", "avenir500": "c5ji2", "avenir500i": "c5qt3", "avenir600": "c5ync", "avenir600i": "c5vw5", "avenir700": "c5j0a", "avenir700i": "c5xmd", "avenir800": "c5h5q", "avenir800i": "c5sr5" }

const actual2 = css (fonts.avenir300)

const expect2 = "c5w4b"

t.deepEqual (actual1, expect1) t.deepEqual (actual2, expect2) }) / eslint-enable max-lines-per-function / #+end_src #+HTML:

**** css

#+HTML: #+begin_src js :tangle src/api/css.js import { cache, getClassName, isArr, merge, parse, update } from "./index.js"

export function css (params) { const input = isArr (params) ? merge (... params) : params

return parse ({ input }) .map (cache) .map (update) .map (getClassName) .filter (Boolean) .join (" ") } #+end_src #+HTML:

#+HTML: #+begin_src js :tangle src/api/css.test.js import ava from "ava" import { store } from "../store/store.js" import { css } from "./css.js"

function strMapToObj (strMap) { const obj = Object.create (null)

for (const k, v of strMap) { objk = v }

return obj }

ava ("given undefined arguments", (t) => { const actual = css () const expect = ""

t.deepEqual (actual, expect) })

ava ("given an object with simple declarations", (t) => { const actual1 = css ({ "backgroundColor": "#f00", "display": "block" })

const expect1 = "jt2a9 drtx9"

const actual2 = strMapToObj (store.get (""))

const expect2 = { '{"background-color":"#f00"}': { "block": { "background-color": "#f00" } , "emit": true, "identifier": "jt2a9", "input": { "backgroundColor": "#f00" }, "insertRule": true, "media": "", "property": "backgroundColor", "selectors": [".jt2a9"], "value": "#f00" }, '{"display":"block"}': { "block": { "display": "block" } , "emit": true, "identifier": "drtx9", "input": { "display": "block" }, "insertRule": true, "media": "", "property": "display", "selectors": [".drtx9"], "value": "block" } }

t.is (actual1, expect1) t.deepEqual (actual2, expect2) })

ava ("given an array of objects with simple declarations", (t) => { const actual = css ( { "backgroundColor": "#f00", "display": "block" }, { "backgroundColor": "#0f0" } )

const expect = "jtz4h drtx9"

t.is (actual, expect) }) #+end_src #+HTML:

**** index

#+HTML: #+begin_src js :tangle src/api/index.js export { create } from "./create.js" export { css } from "./css.js" export { getAncestors } from "../build/get-ancestors.js" export { getBlockString } from "../build/get-block-string.js" export { getClassName } from "../build/get-class-name.js" export { getPlaceholders } from "../build/get-placeholders.js" export { getPropertyId } from "../build/get-property-id.js" export { getSelectors } from "../build/get-selectors.js" export { getSelectorsString } from "../build/get-selectors-string.js" export { getStringHash } from "../build/get-string-hash.js" export { getStyle } from "../build/get-style.js" export { getStyles } from "../build/get-styles.js" export { canUseDom } from "../client/can-use-dom.js" export { getStyleElement } from "../client/get-style-element.js" export { insertRule } from "../client/insert-rule.js" export { update } from "../client/update.js" export { updateStyles } from "../client/update-styles.js" export { parse } from "../parse/parse.js" export { parseFallbacks } from "../parse/parse-fallbacks.js" export { parseFontFace } from "../parse/parse-font-face.js" export { parseIdentifier } from "../parse/parse-identifier.js" export { parseInput } from "../parse/parse-input.js" export { parseKeyframes } from "../parse/parse-keyframes.js" export { parseMedia } from "../parse/parse-media.js" export { parseNumbers } from "../parse/parse-numbers.js" export { parsePlaceholder } from "../parse/parse-placeholder.js" export { parseSelectors } from "../parse/parse-selectors.js" export { parseTypeSelector } from "../parse/parse-type-selector.js" export { cache } from "../store/cache.js" export { store } from "../store/store.js" export { camelCase } from "../utils/camel-case.js" export { debounce } from "../utils/debounce.js" export { kebabCase } from "../utils/kebab-case.js" export { canMerge, cloneObj, emptyObj, isArr, isDef, isNum, isObj, merge, mergeArr, mergeObj } from "../utils/merge.js" export { pubSub } from "../utils/pub-sub.js" export { toPairs } from "../utils/to-pairs.js" #+end_src #+HTML:

**** style

#+HTML: #+begin_src js :tangle src/api/style.js export { create } from "./create.js" export { css } from "./css.js" export { getStyles } from "../build/get-styles.js" export { merge } from "../utils/merge.js" #+end_src #+HTML:

*** Build

**** getAncestors

#+HTML: #+begin_src js :tangle src/build/get-ancestors.js export function getAncestors (ancestors = [], selectors = []) { return selectors.reduce (function (results, selector) { if (ancestors.length) { const index = selector.indexOf ("&")

  ancestors.forEach (function (ancestor) {
    results.push (
      index < 0
        ? ancestor.concat (" ", selector)
        : selector
          .slice (0, index)
          .concat (ancestor, selector.slice (index + 1))
    )
  })

  return results
}

return results.concat ([selector])

}, []) } #+end_src #+HTML:

#+HTML: #+begin_src js :tangle src/build/get-ancestors.test.js import ava from "ava" import { getAncestors } from "./get-ancestors.js"

ava ("given undefined arguments", (t) => { const actual = getAncestors ()

const expect = []

t.deepEqual (actual, expect) })

ava ("given an array of selectors without ancestors", (t) => { const actual = getAncestors ([], ["#root"])

const expect = ["#root"]

t.deepEqual (actual, expect) })

ava ("given an array of selectors with implied ancestor location", (t) => { const actual = getAncestors (["#root"], ["#body"])

const expect = ["#root", " ", "#body"]

t.deepEqual (actual, expect) })

ava ("given an array of selectors with defined ancestor prefix", (t) => { const actual = getAncestors (["#root"], ["&", " ", "#body"])

const expect = ["#root", " ", "#body"]

t.deepEqual (actual, expect) })

ava ("given an array of selectors with defined ancestor suffix", (t) => { const actual = getAncestors (["#root"], ["#body", " ", "&"])

const expect = ["#body", " ", "#root"]

t.deepEqual (actual, expect) })

ava ("given an array of selectors with defined ancestor middle", (t) => { const actual = getAncestors ( ["#root"], ["#body", " ", "&", " ", "%thing"] )

const expect = ["#body", " ", "#root", " ", "%thing"]

t.deepEqual (actual, expect) })

ava ("given an array of selectors with array of ancestors", (t) => { const actual = getAncestors ( ["#root", "#body", "%test"], ["#more", ">", "%stuff", "#thing", " ", "&", "+", "%thing"] )

const expect = [ "#root", "#body", " ", "#more", ">", "%stuff", "%test", " ", "#more", ">", "%stuff", "#thing", " ", "#root", "#body", "+", "%thing", "#thing", " ", "%test", "+", "%thing" ]

t.deepEqual (actual, expect) }) #+end_src #+HTML:

**** getBlockString

#+HTML: #+begin_src js :tangle src/build/get-block-string.js import { isObj, kebabCase, toPairs } from "../api/index.js"

export function getBlockString (params = {}) { const block = params.block || []

let sep = ";"

return block .map (function (rule) { return toPairs (rule).map (function (style) { const property = style0 const value = style1

    if (isObj (value)) {
      const a = toPairs (value)
        .map (function (b) {
          return kebabCase (b[0]).concat (":", b[1])
        })
        .join (";")

      sep = ""

      return "".concat (property, "{", a, "}")
    }

    return "".concat (property, ":", value)
  })
})
.join (sep)

} #+end_src #+HTML:

#+HTML: #+begin_src js :tangle src/build/get-block-string.test.js import ava from "ava" import { getBlockString } from "./get-block-string.js"

ava ("given undefined arguments", (t) => { const actual = getBlockString ()

const expect = ""

t.is (actual, expect) })

ava ("given a block with simple property and value", (t) => { const actual = getBlockString ({ "block": { "background-color": "#f00" } })

const expect = "background-color:#f00"

t.is (actual, expect) })

ava ("given a block with fallback properties and value", (t) => { const actual = getBlockString ({ "block": { "background-color": "#f00" }, { "background-color": "rgba(255, 0, 0, 0.9)" } })

const expect = "background-color:#f00;background-color:rgba(255, 0, 0, 0.9)"

t.is (actual, expect) })

ava ("given a block with keyframes object", (t) => { const actual = getBlockString ({ "block": { "0%": { "backgroundColor": "#f00", "opacity": 0 } }, { "100%": { "backgroundColor": "#0f0", "opacity": 1 } } })

const expect = "0%{background-color:#f00;opacity:0}100%{background-color:#0f0;opacity:1}"

t.is (actual, expect) }) #+end_src #+HTML:

**** getClassName

#+HTML: #+begin_src js :tangle src/build/get-class-name.js export function getClassName (params = {}) { const emit = params.emit const identifier = params.identifier

return emit ? identifier : null } #+end_src #+HTML:

#+HTML: #+begin_src js :tangle src/build/get-class-name.test.js import ava from "ava" import { getClassName } from "./get-class-name.js"

ava ("given undefined arguments", (t) => { const actual = getClassName ()

const expect = null

t.deepEqual (actual, expect) })

ava ("given an object with identifier and emit true", (t) => { const actual = getClassName ({ "emit": true, "identifier": "jtz4h", "property": "backgroundColor", "selectors": [".jtz4h"], "value": "#0f0" })

const expect = "jtz4h"

t.deepEqual (actual, expect) })

ava ("given an object with identifier and emit false", (t) => { const actual = getClassName ({ "block": { "src": "url('/fonts/font.woff2') format ('woff2'), url('/fonts/font.woff') format ('woff')" }, { "font-family": "c5xq1" } , "emit": false, "identifier": "c5xq1", "input": { "fontFamily": { "src": "url('/fonts/font.woff2') format ('woff2'), url('/fonts/font.woff') format ('woff')" } }, "media": "", "property": "fontFamily", "selectors": ["@font-face"], "value": { "src": "url('/fonts/font.woff2') format ('woff2'), url('/fonts/font.woff') format ('woff')" } })

const expect = null

t.deepEqual (actual, expect) }) #+end_src #+HTML:

**** getPlaceholders

#+HTML: #+begin_src js :tangle src/build/get-placeholders.js import { parseIdentifier } from "../api/index.js"

export function getPlaceholders (selectors = []) { return selectors.map (function (selector) { return (/^%/u).test (selector) ? ".".concat ( parseIdentifier ({ "property": selector, "value": selector }).identifier ) : selector }) } #+end_src #+HTML:

#+HTML: #+begin_src js :tangle src/build/get-placeholders.test.js import ava from "ava" import { getPlaceholders } from "./get-placeholders.js"

ava ("given undefined arguments", (t) => { const actual = getPlaceholders ()

const expect = []

t.deepEqual (actual, expect) })

ava ("given an array of selectors with placeholders", (t) => { const actual = getPlaceholders ("a", "%b", ".c", "%products", "%items")

const expect = "a", ".afqkz", ".c", ".afknd", ".afxpz"

t.deepEqual (actual, expect) }) #+end_src #+HTML:

**** getPropertyId

#+HTML: #+begin_src js :tangle src/build/get-property-id.js import { camelCase } from "../api/index.js"

/**

  • @param {string} propertyName
    • Property name/identifier specifying a stylistic CSS feature to change.
  • @returns {number} */

export function getPropertyId (propertyName = "") { const n = parseInt ("af", 36)

switch (true) { case (/^%/u).test (propertyName): return 0 + n case (/^\x2D\x2D/u).test (propertyName): return 1 + n default: return ( "$,--,all,direction,unicodeBidi,writingMode,textOrientation,glyphOrientationVertical,textCombineUpright,textTransform,whiteSpace,textSpaceCollapse,textSpaceTrim,tabSize,wordBreak,lineBreak,hyphens,overflowWrap,wordWrap,textWrap,wrapBefore,wrapAfter,wrapInside,hyphenateCharacter,hyphenateLimitZone,hyphenateLimitChars,hyphenateLimitLines,hyphenateLimitLast,textAlign,textAlignAll,textAlignLast,textJustify,textGroupAlign,wordSpacing,letterSpacing,linePadding,textSpacing,textIndent,hangingPunctuation,textDecoration,textDecorationLine,textDecorationStyle,textDecorationColor,textDecorationWidth,textDecorationSkip,textDecorationSkipInk,textUnderlineOffset,textUnderlinePosition,textEmphasis,textEmphasisStyle,textEmphasisColor,textEmphasisPosition,textEmphasisSkip,textShadow,src,font,fontStyle,fontVariant,fontWeight,fontStretch,fontSize,lineHeight,fontFamily,fontMinSize,fontMaxSize,fontSizeAdjust,fontSynthesis,fontSynthesisWeight,fontSynthesisStyle,fontSynthesisSmallCaps,unicodeRange,fontFeatureSettings,fontVariationSettings,fontLanguageOverride,fontKerning,fontVariantLigatures,fontVariantPosition,fontVariantCaps,fontVariantNumeric,fontVariantAlternates,fontVariantEastAsian,fontOpticalSizing,fontPalette,fontVariantEmoji,content,quotes,stringSet,bookmarkLevel,bookmarkLabel,bookmarkState,running,footnoteDisplay,footnotePolicy,outline,outlineColor,outlineStyle,outlineWidth,outlineOffset,resize,textOverflow,cursor,caret,caretColor,caretShape,navUp,navRight,navDown,navLeft,userSelect,appearance,position,top,right,bottom,left,offsetBefore,offsetAfter,offsetStart,offsetEnd,zIndex,display,contain,width,height,minWidth,minHeight,maxWidth,maxHeight,boxSizing,visibility,pageBreakBefore,pageBreakAfter,pageBreakInside,margin,marginTop,marginRight,marginBottom,marginLeft,marginTrim,padding,paddingTop,paddingRight,paddingBottom,paddingLeft,dominantBaseline,verticalAlign,alignmentBaseline,baselineShift,inlineSizing,initialLetters,initialLettersAlign,initialLettersWrap,listStyle,listStyleType,listStylePosition,listStyleImage,markerSide,counterReset,counterSet,counterIncrement,overflow,overflowX,overflowY,overflowBlock,overflowInline,blockOverflow,lineClamp,maxLines,continue,tableLayout,borderCollapse,borderSpacing,captionSide,emptyCells,flexFlow,flexDirection,flexWrap,order,flex,flexGrow,flexShrink,flexBasis,placeContent,alignContent,justifyContent,placeItems,alignItems,justifyItems,placeSelf,alignSelf,justifySelf,gap,rowGap,columnGap,columns,columnWidth,columnCount,columnRule,columnRuleWidth,columnRuleStyle,columnRuleColor,columnSpan,columnFill,flowInto,flowFrom,regionFragment,breakBefore,breakAfter,breakInside,orphans,widows,boxDecorationBreak,grid,gridTemplate,gridTemplateRows,gridTemplateColumns,gridTemplateAreas,gridAutoFlow,gridAutoRows,gridAutoColumns,gridArea,gridRow,gridRowStart,gridRowEnd,gridColumn,gridColumnStart,gridColumnEnd,rubyPosition,rubyMerge,rubyAlign,float,clear,blockSize,inlineSize,minBlockSize,minInlineSize,maxBlockSize,maxInlineSize,marginBlock,marginBlockStart,marginBlockEnd,marginInline,marginInlineStart,marginInlineEnd,inset,insetBlock,insetBlockStart,insetBlockEnd,insetInline,insetInlineStart,insetInlineEnd,paddingBlock,paddingBlockStart,paddingBlockEnd,paddingInline,paddingInlineStart,paddingInlineEnd,borderBlockWidth,borderBlockStartWidth,borderBlockEndWidth,borderInlineWidth,borderInlineStartWidth,borderInlineEndWidth,borderBlockStyle,borderBlockStartStyle,borderBlockEndStyle,borderInlineStyle,borderInlineStartStyle,borderInlineEndStyle,borderBlockColor,borderBlockStartColor,borderBlockEndColor,borderInlineColor,borderInlineStartColor,borderInlineEndColor,borderBlock,borderBlockStart,borderBlockEnd,borderInline,borderInlineStart,borderInlineEnd,borderStartStartRadius,borderStartEndRadius,borderEndStartRadius,borderEndEndRadius,fillRule,fillBreak,fill,fillColor,fillImage,fillOrigin,fillPosition,fillSize,fillRepeat,fillOpacity,strokeWidth,strokeAlign,strokeLinecap,strokeLinejoin,strokeMiterlimit,strokeBreak,strokeDasharray,strokeDashoffset,strokeDashCorner,strokeDashJustify,stroke,strokeColor,strokeImage,strokeOrigin,strokePosition,strokeSize,strokeRepeat,strokeOpacity,marker,markerStart,markerMid,markerEnd,markerSegment,markerPattern,markerKnockoutLeft,markerKnockoutRight,vectorEffect,colorRendering,shapeRendering,textRendering,imageRendering,bufferedRendering,stopColor,stopOpacity,color,opacity,colorAdjust,objectFit,objectPosition,imageResolution,imageOrientation,imageRendering,background,backgroundColor,backgroundImage,backgroundPosition,backgroundPositionX,backgroundPositionY,backgroundSize,backgroundRepeat,backgroundAttachment,backgroundOrigin,backgroundClip,border,borderTop,borderRight,borderBottom,borderLeft,borderWidth,borderTopWidth,borderRightWidth,borderBottomWidth,borderLeftWidth,borderStyle,borderTopStyle,borderRightStyle,borderBottomStyle,borderLeftStyle,borderColor,borderTopColor,borderRightColor,borderBottomColor,borderLeftColor,borderRadius,borderTopLeftRadius,borderTopRightRadius,borderBottomRightRadius,borderBottomLeftRadius,borderImage,borderImageSource,borderImageSlice,borderImageWidth,borderImageOutset,borderImageRepeat,boxShadow,clip,clipPath,clipRule,mask,maskImage,maskPosition,maskSize,maskRepeat,maskOrigin,maskClip,maskComposite,maskMode,maskBorder,maskBorderSource,maskBorderSlice,maskBorderWidth,maskBorderOutset,maskBorderRepeat,maskBorderMode,maskType,shapeOutside,shapeImageThreshold,shapeMargin,filter,floodColor,floodOpacity,colorInterpolationFilters,lightingColor,mixBlendMode,isolation,backgroundBlendMode,transition,transitionProperty,transitionDuration,transitionTimingFunction,transitionDelay,transform,transformOrigin,transformBox,transformStyle,perspective,perspectiveOrigin,backfaceVisibility,animation,animationName,animationDuration,animationTimingFunction,animationDelay,animationIterationCount,animationDirection,animationFillMode,animationPlayState,offset,offsetPosition,offsetPath,offsetDistance,offsetRotate,offsetAnchor,willChange,scrollSnapType,scrollPadding,scrollPaddingTop,scrollPaddingRight,scrollPaddingBottom,scrollPaddingLeft,scrollPaddingBlock,scrollPaddingBlockStart,scrollPaddingBlockEnd,scrollPaddingInline,scrollPaddingInlineStart,scrollPaddingInlineEnd,scrollMargin,scrollMarginTop,scrollMarginRight,scrollMarginBottom,scrollMarginLeft,scrollMarginBlock,scrollMarginBlockStart,scrollMarginBlockEnd,scrollMarginInline,scrollMarginInlineStart,scrollMarginInlineEnd,scrollSnapAlign,scrollSnapStop,scrollBehavior" .split (",") .indexOf ( camelCase (propertyName) .replace (/^(Moz|Ms|Webkit)/u, "") .replace (/^(A-Z)/u, function (a) { return a.toLowerCase () }) ) + n ) } } #+end_src #+HTML:

#+HTML: #+begin_src json :tangle src/build/get-property-id.json { "variables": "%", "--" ,

"cascade": "all" ,

"writingModes": "direction", "unicodeBidi", "writingMode", "textOrientation", "glyphOrientationVertical", "textCombineUpright" ,

"text": [ "textTransform",

"whiteSpace",
"textSpaceCollapse",
"textSpaceTrim",
"tabSize",

"wordBreak",
"lineBreak",
"hyphens",
"overflowWrap",
"wordWrap",

"textWrap",
"wrapBefore",
"wrapAfter",
"wrapInside",

"hyphenateCharacter",
"hyphenateLimitZone",
"hyphenateLimitChars",
"hyphenateLimitLines",
"hyphenateLimitLast",

"textAlign",
"textAlignAll",
"textAlignLast",
"textJustify",
"textGroupAlign",

"wordSpacing",
"letterSpacing",
"linePadding",
"textSpacing",

"textIndent",
"hangingPunctuation"

],

"textDecor": [ "textDecoration", "textDecorationLine", "textDecorationStyle", "textDecorationColor",

"textDecorationWidth",
"textDecorationSkip",
"textDecorationSkipInk",

"textUnderlineOffset",
"textUnderlinePosition",

"textEmphasis",
"textEmphasisStyle",
"textEmphasisColor",

"textEmphasisPosition",

"textEmphasisSkip",

"textShadow"

],

"fonts": [ "src",

"font",
"fontStyle",
"fontVariant",
"fontWeight",
"fontStretch",
"fontSize",
"lineHeight",
"fontFamily",

"fontMinSize",
"fontMaxSize",
"fontSizeAdjust",

"fontSynthesis",
"fontSynthesisWeight",
"fontSynthesisStyle",
"fontSynthesisSmallCaps",

"unicodeRange",

"fontFeatureSettings",
"fontVariationSettings",
"fontLanguageOverride",

"fontKerning",

"fontVariantLigatures",
"fontVariantPosition",
"fontVariantCaps",
"fontVariantNumeric",
"fontVariantAlternates",
"fontVariantEastAsian",

"fontOpticalSizing",

"fontPalette",
"fontVariantEmoji"

],

"content": "content", "quotes", "stringSet", "bookmarkLevel", "bookmarkLabel", "bookmarkState" ,

"gcpm": "running", "footnoteDisplay", "footnotePolicy" ,

"ui": [ "outline", "outlineColor", "outlineStyle", "outlineWidth",

"outlineOffset",
"resize",
"textOverflow",
"cursor",

"caret",
"caretColor",
"caretShape",

"navUp",
"navRight",
"navDown",
"navLeft",

"userSelect",
"appearance"

],

"position": [ "position",

"top",
"right",
"bottom",
"left",

"offsetBefore",
"offsetAfter",
"offsetStart",
"offsetEnd",

"zIndex"

],

"display": "display" ,

"contain": "contain" ,

"sizing": [ "width", "height",

"minWidth",
"minHeight",

"maxWidth",
"maxHeight",

"boxSizing"

],

"css2": [ "visibility",

"pageBreakBefore",
"pageBreakAfter",
"pageBreakInside"

],

"box": [ "margin", "marginTop", "marginRight", "marginBottom", "marginLeft",

"marginTrim",

"padding",
"paddingTop",
"paddingRight",
"paddingBottom",
"paddingLeft"

],

"inline": [ "dominantBaseline", "verticalAlign", "alignmentBaseline", "baselineShift",

"inlineSizing",

"initialLetters",
"initialLettersAlign",
"initialLettersWrap"

],

"lists": [ "listStyle", "listStyleType", "listStylePosition", "listStyleImage",

"markerSide",

"counterReset",
"counterSet",
"counterIncrement"

],

"overflow": [ "overflow", "overflowX", "overflowY",

"overflowBlock",
"overflowInline",

"blockOverflow",
"lineClamp",
"maxLines",
"continue"

],

"tables": [ "tableLayout", "borderCollapse", "borderSpacing", "captionSide",

"emptyCells"

],

"flexbox": [ "flexFlow", "flexDirection", "flexWrap",

"order",

"flex",
"flexGrow",
"flexShrink",
"flexBasis"

],

"align": [ "placeContent", "alignContent", "justifyContent",

"placeItems",
"alignItems",
"justifyItems",

"placeSelf",
"alignSelf",
"justifySelf",

"gap",
"rowGap",
"columnGap"

],

"multicol": [ "columns", "columnWidth", "columnCount",

"columnRule",
"columnRuleWidth",
"columnRuleStyle",
"columnRuleColor",

"columnSpan",
"columnFill"

],

"regions": "flowInto", "flowFrom", "regionFragment" ,

"break": "breakBefore", "breakAfter", "breakInside", "orphans", "widows", "boxDecorationBreak" ,

"grid": [ "grid",

"gridTemplate",
"gridTemplateRows",
"gridTemplateColumns",
"gridTemplateAreas",

"gridAutoFlow",
"gridAutoRows",
"gridAutoColumns",

"gridArea",

"gridRow",
"gridRowStart",
"gridRowEnd",

"gridColumn",
"gridColumnStart",
"gridColumnEnd"

],

"ruby": "rubyPosition", "rubyMerge", "rubyAlign" ,

"logical": [ "float", "clear",

"blockSize",
"inlineSize",
"minBlockSize",
"minInlineSize",
"maxBlockSize",
"maxInlineSize",

"marginBlock",
"marginBlockStart",
"marginBlockEnd",

"marginInline",
"marginInlineStart",
"marginInlineEnd",

"inset",
"insetBlock",
"insetBlockStart",
"insetBlockEnd",
"insetInline",
"insetInlineStart",
"insetInlineEnd",

"paddingBlock",
"paddingBlockStart",
"paddingBlockEnd",
"paddingInline",
"paddingInlineStart",
"paddingInlineEnd",

"borderBlockWidth",
"borderBlockStartWidth",
"borderBlockEndWidth",
"borderInlineWidth",
"borderInlineStartWidth",
"borderInlineEndWidth",

"borderBlockStyle",
"borderBlockStartStyle",
"borderBlockEndStyle",
"borderInlineStyle",
"borderInlineStartStyle",
"borderInlineEndStyle",

"borderBlockColor",
"borderBlockStartColor",
"borderBlockEndColor",
"borderInlineColor",
"borderInlineStartColor",
"borderInlineEndColor",

"borderBlock",
"borderBlockStart",
"borderBlockEnd",
"borderInline",
"borderInlineStart",
"borderInlineEnd",

"borderStartStartRadius",
"borderStartEndRadius",
"borderEndStartRadius",
"borderEndEndRadius"

],

"fillStroke": [ "fillRule", "fillBreak",

"fill",
"fillColor",
"fillImage",
"fillOrigin",
"fillPosition",
"fillSize",
"fillRepeat",

"fillOpacity",

"strokeWidth",
"strokeAlign",
"strokeLinecap",
"strokeLinejoin",
"strokeMiterlimit",
"strokeBreak",
"strokeDasharray",
"strokeDashoffset",
"strokeDashCorner",
"strokeDashJustify",

"stroke",
"strokeColor",
"strokeImage",
"strokeOrigin",
"strokePosition",
"strokeSize",
"strokeRepeat",

"strokeOpacity"

],

"svgMarkers": [ "marker", "markerStart", "markerMid", "markerEnd",

"markerSegment",
"markerPattern",

"markerKnockoutLeft",
"markerKnockoutRight"

],

"svgTiny": [ "vectorEffect",

"colorRendering",
"shapeRendering",
"textRendering",
"imageRendering",
"bufferedRendering",

"stopColor",
"stopOpacity"

],

"color": [ "color", "opacity",

"colorAdjust"

],

"images": [ "objectFit", "objectPosition",

"imageResolution",
"imageOrientation",
"imageRendering"

],

"backgrounds": [ "background", "backgroundColor", "backgroundImage", "backgroundPosition", "backgroundPositionX", "backgroundPositionY", "backgroundSize", "backgroundRepeat", "backgroundAttachment", "backgroundOrigin", "backgroundClip",

"border",
"borderTop",
"borderRight",
"borderBottom",
"borderLeft",

"borderWidth",
"borderTopWidth",
"borderRightWidth",
"borderBottomWidth",
"borderLeftWidth",

"borderStyle",
"borderTopStyle",
"borderRightStyle",
"borderBottomStyle",
"borderLeftStyle",

"borderColor",
"borderTopColor",
"borderRightColor",
"borderBottomColor",
"borderLeftColor",

"borderRadius",
"borderTopLeftRadius",
"borderTopRightRadius",
"borderBottomRightRadius",
"borderBottomLeftRadius",

"borderImage",
"borderImageSource",
"borderImageSlice",
"borderImageWidth",
"borderImageOutset",
"borderImageRepeat",

"boxShadow"

],

"masking": [ "clip", "clipPath", "clipRule",

"mask",
"maskImage",
"maskPosition",
"maskSize",
"maskRepeat",
"maskOrigin",
"maskClip",
"maskComposite",
"maskMode",

"maskBorder",
"maskBorderSource",
"maskBorderSlice",
"maskBorderWidth",
"maskBorderOutset",
"maskBorderRepeat",
"maskBorderMode",

"maskType"

],

"shapes": "shapeOutside", "shapeImageThreshold", "shapeMargin" ,

"filterEffects": "filter", "floodColor", "floodOpacity", "colorInterpolationFilters", "lightingColor" ,

"compositing": "mixBlendMode", "isolation", "backgroundBlendMode" ,

"transitions": "transition", "transitionProperty", "transitionDuration", "transitionTimingFunction", "transitionDelay" ,

"transforms": [ "transform", "transformOrigin", "transformBox", "transformStyle",

"perspective",
"perspectiveOrigin",
"backfaceVisibility"

],

"animations": "animation", "animationName", "animationDuration", "animationTimingFunction", "animationDelay", "animationIterationCount", "animationDirection", "animationFillMode", "animationPlayState" ,

"motion": "offset", "offsetPosition", "offsetPath", "offsetDistance", "offsetRotate", "offsetAnchor" ,

"willChange": "willChange" ,

"scrollSnap": [ "scrollSnapType",

"scrollPadding",
"scrollPaddingTop",
"scrollPaddingRight",
"scrollPaddingBottom",
"scrollPaddingLeft",

"scrollPaddingBlock",
"scrollPaddingBlockStart",
"scrollPaddingBlockEnd",

"scrollPaddingInline",
"scrollPaddingInlineStart",
"scrollPaddingInlineEnd",

"scrollMargin",
"scrollMarginTop",
"scrollMarginRight",
"scrollMarginBottom",
"scrollMarginLeft",

"scrollMarginBlock",
"scrollMarginBlockStart",
"scrollMarginBlockEnd",

"scrollMarginInline",
"scrollMarginInlineStart",
"scrollMarginInlineEnd",

"scrollSnapAlign",
"scrollSnapStop"

],

"cssomView": "scrollBehavior" } #+end_src #+HTML:

#+HTML: #+begin_src js :tangle src/build/get-property-id.test.js import ava from "ava" import { getPropertyId } from "./get-property-id.js"

ava ("given undefined arguments", (t) => { const actual = getPropertyId () const expect = 374

t.is (actual, expect) })

ava ("given an empty string", (t) => { const actual = getPropertyId ("") const expect = 374

t.is (actual, expect) })

ava ("given a string with an invalid property name", (t) => { const actual = getPropertyId ("xyz") const expect = 374

t.is (actual, expect) })

ava ("given a string with a valid placeholder class name", (t) => { const actual = getPropertyId ("%productList") const expect = 375

t.is (actual, expect) })

ava ("given a string with a valid CSS variable name", (t) => { const actual = getPropertyId ("--background-color") const expect = 376

t.is (actual, expect) })

ava ("given a string with a valid property name (1)", (t) => { const actual = getPropertyId ("background") const expect = 712

t.is (actual, expect) })

ava ("given a string with a valid property name (2)", (t) => { const actual = getPropertyId ("background-color") const expect = 713

t.is (actual, expect) })

ava ("given a string with a prefixed property name (1)", (t) => { const actual = getPropertyId ("-webkit-appearance") const expect = 484

t.is (actual, expect) })

ava ("given a string with a prefixed property name (2)", (t) => { const actual = getPropertyId ("MozUserSelect") const expect = 483

t.is (actual, expect) }) #+end_src #+HTML:

**** getSelectors

#+HTML: #+begin_src js :tangle src/build/get-selectors.js /**

  • @param {string} selectors
    • String identifying the elements to which a set of CSS rulesets apply.
  • @returns {?RegExpMatchArray} */

export function getSelectors (selectors = "") { const identifier = "-?A-Z_a-z\u{00a0}-\u{ffff}+-0-9A-Z_a-z\u{00a0}-\u{ffff}*"

const regex = new RegExp ( [ "(&)",

  "(#".concat (identifier, ")"),

  "(\\.".concat (identifier, ")"),
  "(\\$".concat (identifier, ")"),
  "(%".concat (identifier, ")"),
  "(\\^".concat (identifier, ")"),
  "(\\[[-$*0-9=A-Z^_a-z|~\\u{00a0}-\\u{ffff}]+\\])",
  "(::?".concat (identifier, ")"),

  "(".concat (identifier, ")"),

  "(\\*)",

  "([ +>~]+)"
].join ("|"),
"gu"

)

return selectors.split (",").map (function (selector) { return selector .trim () .match (regex) .map (function (str) { return str.trim ().replace (/^$/u, " ") }) }) } #+end_src #+HTML:

#+HTML: #+begin_src js :tangle src/build/get-selectors.test.js import ava from "ava" import { getSelectors } from "./get-selectors.js"

ava ("given undefined arguments", (t) => { t.throws (() => getSelectors ()) })

ava ("given a string with ancestor selector", (t) => { const actual = getSelectors ("&") const expect = ["&"]

t.deepEqual (actual, expect) })

ava ("given a string with an id selector", (t) => { const actual = getSelectors ("#a") const expect = ["#a"]

t.deepEqual (actual, expect) })

ava ("given a string with a class selector", (t) => { const actual = getSelectors (".a") const expect = [".a"]

t.deepEqual (actual, expect) })

ava ("given a string with a suffix selector", (t) => { const actual = getSelectors ("$a") const expect = ["$a"]

t.deepEqual (actual, expect) })

ava ("given a string with a placeholder selector", (t) => { const actual = getSelectors ("%a") const expect = ["%a"]

t.deepEqual (actual, expect) })

ava ("given a string with a prefix selector", (t) => { const actual = getSelectors ("^a") const expect = ["^a"]

t.deepEqual (actual, expect) })

ava ("given a string with an attribute selector (1)", (t) => { const actual = getSelectors ("a") const expect = [["a"]]

t.deepEqual (actual, expect) })

ava ("given a string with an attribute selector (2)", (t) => { const actual = getSelectors ("class=x") const expect = [["class=x"]]

t.deepEqual (actual, expect) })

ava ("given a string with a pseudo-class selector", (t) => { const actual = getSelectors (":first-child") const expect = [":first-child"]

t.deepEqual (actual, expect) })

ava ("given a string with a pseudo-element selector", (t) => { const actual = getSelectors ("::after") const expect = ["::after"]

t.deepEqual (actual, expect) })

ava ("given a string with a type selector", (t) => { const actual = getSelectors ("a") const expect = ["a"]

t.deepEqual (actual, expect) })

ava ("given a string with a universal selector", (t) => { const actual = getSelectors ("") const expect = [[""]]

t.deepEqual (actual, expect) })

ava ("given a string with a complex selector", (t) => { const actual = getSelectors ("&#a.b%cd::after li ~ ") const expect = [ ["&", "#a", ".b", "%c", "d", "::after", " ", "li", "~", ""] ]

t.deepEqual (actual, expect) })

ava ("given a string with multiple complex selectors", (t) => { const actual = getSelectors ("& > div::hover, liaria-expanded=true") const expect = [ "&", ">", "div", "::hover", ["li", "aria-expanded=true"] ]

t.deepEqual (actual, expect) }) #+end_src #+HTML:

**** getSelectorsString

#+HTML: #+begin_src js :tangle src/build/get-selectors-string.js export function getSelectorsString (params = {}) { const selectors = params.selectors || []

return selectors .map (function (selector) { return selector.join ("") }) .join (",") } #+end_src #+HTML:

#+HTML: #+begin_src js :tangle src/build/get-selectors-string.test.js import ava from "ava" import { getSelectorsString } from "./get-selectors-string.js"

ava ("given undefined arguments", (t) => { const actual = getSelectorsString ()

const expect = ""

t.is (actual, expect) })

ava ("given an object with empty selectors (1)", (t) => { const actual = getSelectorsString ({ "selectors": [] })

const expect = ""

t.is (actual, expect) })

ava ("given an object with empty selectors (2)", (t) => { const actual = getSelectorsString ({ "selectors": [[]] })

const expect = ""

t.is (actual, expect) })

ava ("given an object with single selector (1)", (t) => { const actual = getSelectorsString ({ "selectors": [".abcde"] })

const expect = ".abcde"

t.is (actual, expect) })

ava ("given an object with single selector (2)", (t) => { const actual = getSelectorsString ({ "selectors": [".abcde", ">", ".fghij"] })

const expect = ".abcde>.fghij"

t.is (actual, expect) })

ava ("given an object with multiple selectors (1)", (t) => { const actual = getSelectorsString ({ "selectors": [".abcde", ".fghij"] })

const expect = ".abcde,.fghij"

t.is (actual, expect) })

ava ("given an object with multiple selectors (2)", (t) => { const actual = getSelectorsString ({ "selectors": [".abcde", ":hover", ".fghij"] })

const expect = ".abcde:hover,.fghij"

t.is (actual, expect) }) #+end_src #+HTML:

**** getStringHash

#+HTML: #+begin_src js :tangle src/build/get-string-hash.js /**

  • Converts string to unique hash identifier string.
  • @param {string} string
    • The string to convert.
  • @returns {string}
  • The string hash identifier. */

export function getStringHash (string = "") { return string .split ("") .reduce (function (i, str) { return i << 5 ^ i ^ str.charCodeAt () & 0xffffffffff }, 5381 << 2) .toString (36) } #+end_src #+HTML:

#+HTML: #+begin_src js :tangle src/build/get-string-hash.test.js import ava from "ava" import { getStringHash } from "./get-string-hash.js"

ava ("given undefined arguments", (t) => { const actual = getStringHash ().slice (-3) const expect = "glw"

t.is (actual, expect) })

ava ("given an empty string", (t) => { const actual = getStringHash ("").slice (-3) const expect = "glw"

t.is (actual, expect) })

ava ("given a simple string (1)", (t) => { const actual = getStringHash ("abc").slice (-3) const expect = "ed0"

t.is (actual, expect) })

ava ("given a simple string (2)", (t) => { const actual = getStringHash ("abcd").slice (-3) const expect = "47k"

t.is (actual, expect) }) #+end_src #+HTML:

**** getStyle

#+HTML: #+begin_src js :tangle src/build/get-style.js import { getBlockString, getSelectorsString, isDef } from "../api/index.js"

export function getStyle (params = {}, mq = false) { const media = mq && params.media const property = params.property

return (/^%/u).test (property) || !isDef (property) ? null : "".concat ( media ? "@media ".concat (media, "{") : "", getSelectorsString (params), "{", getBlockString (params), "}", media ? "}" : "" ) } #+end_src #+HTML:

#+HTML: #+begin_src js :tangle src/build/get-style.test.js import ava from "ava" import { getStyle } from "./get-style.js"

ava ("given undefined arguments", (t) => { const actual = getStyle () const expect = null

t.is (actual, expect) })

ava ("given an object with valid params", (t) => { const actual = getStyle ({ "block": { "background-color": "#f00" }, "property": "backgroundColor", "selectors": [".jt2a9"] })

const expect = ".jt2a9{background-color:#f00}"

t.is (actual, expect) })

ava ("given an object with valid params and mq = true", (t) => { const actual = getStyle ( { "block": { "background-color": "#f00" }, "property": "backgroundColor", "selectors": [".jt2a9"] }, true )

const expect = ".jt2a9{background-color:#f00}"

t.is (actual, expect) })

ava ("given an object with valid params and media", (t) => { const actual = getStyle ({ "block": { "background-color": "#f00" }, "media": "(min-width: 768px)", "property": "backgroundColor", "selectors": [".jt2a9"] })

const expect = ".jt2a9{background-color:#f00}"

t.is (actual, expect) })

ava ("given an object with valid params, media, mq = true", (t) => { const actual = getStyle ( { "block": { "background-color": "#f00" }, "media": "(min-width: 768px)", "property": "backgroundColor", "selectors": [".jt2a9"] }, true )

const expect = "@media (min-width: 768px){.jt2a9{background-color:#f00}}"

t.is (actual, expect) }) #+end_src #+HTML:

**** getStyles

#+HTML: #+begin_src js :tangle src/build/get-styles.js import { getStyle, store } from "../api/index.js"

export function getStyles () { let results = []

store.forEach (function (rules, media) { let styles = []

rules.forEach (function (style) {
  styles.push (getStyle (style))
})

styles = styles.sort ()

if (media) {
  styles.unshift ("@media ".concat (media, "{"))
  styles.push ("}")
}

results = results.concat (styles)

})

return results.filter (Boolean).join ("") } #+end_src #+HTML:

#+HTML: #+begin_src js :tangle src/build/get-styles.test.js import ava from "ava" import { css } from "../api/css.js" import { cache } from "../store/cache.js" import { getStyles } from "./get-styles.js"

ava.serial ("given undefined arguments", (t) => { const actual = getStyles () const expect = ""

t.is (actual, expect) })

ava.serial ("given an object with placeholder property", (t) => { cache ({ "block": { "%figure": true }, "emit": true, "identifier": "af3qz", "input": { "%figure": true }, "media": "", "property": "%figure", "selectors": [], "value": true })

const actual = getStyles ()

const expect = ""

t.is (actual, expect) })

ava.serial ("given an object with simple property and value", (t) => { cache ({ "block": { "background-color": "#f00" } , "identifier": "jt2a9", "property": "backgroundColor", "selectors": [".jt2a9"], "value": "#f00" })

const actual = getStyles ()

const expect = ".jt2a9{background-color:#f00}"

t.is (actual, expect) })

/ eslint-disable sort-keys / ava.serial ("given a sample declaration", (t) => { css ({ "backgroundColor": "#0f0", "@media (min-width: 768px)": { "backgroundColor": "#f00" } })

const actual = getStyles ()

const expect = ".jt2a9{background-color:#f00}.jtz4h{background-color:#0f0}@media (min-width: 768px){.jtdpi{background-color:#f00}}"

t.is (actual, expect) }) / eslint-enable sort-keys /

ava.serial ("given an object with simple property and value (2)", (t) => { cache ({ "block": { "background-color": "#f00" } , "emit": true, "identifier": "jtdzh", "input": { "background-color": "#f00" }, "media": "", "property": "backgroundColor", "selectors": [".jtdzh", ":hover"], "value": "#f00" })

const actual = getStyles ()

const expect = ".jt2a9,.jtdzh:hover{background-color:#f00}.jtz4h{background-color:#0f0}@media (min-width: 768px){.jtdpi{background-color:#f00}}"

t.is (actual, expect) }) #+end_src #+HTML:

*** Client

**** canUseDom

#+HTML: #+begin_src js :tangle src/client/can-use-dom.js / istanbul ignore next /

/**

  • @returns {boolean} */

export const canUseDom = Boolean ( typeof window !== "undefined" && window.document && window.document.createElement ) #+end_src #+HTML:

**** getStyleElement

#+HTML: #+begin_src js :tangle src/client/get-style-element.js / istanbul ignore next /

export const getStyleElement = (function () { let styles

return function (media = "") { if (typeof styles === "undefined") { styles = document.querySelectorAll ("styledata-creator='@amory/style'") }

let style

for (style of styles) {
  if (style.media === media) {
    return style
  }
}

style = document.createElement ("style")
style.setAttribute ("data-creator", "@amory/style")

if (media.length) {
  style.media = media
}

document.head.appendChild (style)

styles = document.querySelectorAll ("style[data-creator='@amory/style']")

return style

} }) () #+end_src #+HTML:

#+HTML: #+begin_src js :tangle src/client/get-style-element.test.js / eslint-disable max-len / / global browser, page /

import ava from "ava" import http from "http" import puppeteer from "puppeteer" import { getStyleElement } from "./get-style-element.js"

function httpContent (content = "") { return <!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><title> </title>${content}</head></html> }

ava.before (async () => { http .createServer ((request, response) => { response.setHeader ("Content-type", "application/xhtml+xml")

  switch (request.url) {
    case "/a":
      return response.end (httpContent ())
    case "/b":
      return response.end (
        httpContent (
          `<style data-creator="@amory/style">.b{all:inherit}</style>`
        )
      )
    case "/c":
      return response.end (
        httpContent (
          `<style data-creator="@amory/style" media="(min-width: 768px)">.c{gap:1px}</style>`
        )
      )
  }

  return response.end ()
})
.listen (7000)

global.browser = await puppeteer.launch () })

ava.beforeEach (async () => { global.page = await browser.newPage () })

ava.afterEach.always (async () => { await page.close () })

ava.after.always (async () => { await browser.close () })

ava.serial ("given URL '/a', verify returned contents (1)", async (t) => { await page.goto ("http://localhost:7000/a", { "waitUntil": "networkidle0" })

const actual = await page.content ()

const expect = httpContent ()

t.is (actual, expect) })

ava.serial ("given URL '/a', create a new style element (2)", async (t) => { await page.goto ("http://localhost:7000/a", { "waitUntil": "networkidle0" })

const style = await page .evaluateHandle (getStyleElement) .then ((el) => el._remoteObject.className)

const actual = await page.content ()

const expect = httpContent (<style data-creator="@amory/style"></style>)

t.is (style, "HTMLStyleElement") t.is (actual, expect) })

ava.serial ("given URL '/a', create a new style element (3)", async (t) => { await page.goto ("http://localhost:7000/a", { "waitUntil": "networkidle0" })

const style = await page .evaluateHandle (getStyleElement, "(min-width: 768px)") .then ((el) => el._remoteObject.className)

const actual = await page.content ()

const expect = httpContent ( <style data-creator="@amory/style" media="(min-width: 768px)"></style> )

t.is (style, "HTMLStyleElement") t.is (actual, expect) })

ava.serial ("given URL '/b', verify returned contents (1)", async (t) => { await page.goto ("http://localhost:7000/b", { "waitUntil": "networkidle0" })

const actual = await page.content ()

const expect = httpContent ( <style data-creator="@amory/style">.b{all:inherit}</style> )

t.is (actual, expect) })

ava.serial ( "given URL '/b', should re-use existing style element (2)", async (t) => { await page.goto ("http://localhost:7000/b", { "waitUntil": "networkidle0" })

const style = await page
  .evaluateHandle (getStyleElement)
  .then ((el) => el._remoteObject.className)

const actual = await page.content ()

const expect = httpContent (
  `<style data-creator="@amory/style">.b{all:inherit}</style>`
)

t.is (style, "HTMLStyleElement")
t.is (actual, expect)

} )

ava.serial ( "given URL '/b', should create additional style element (3)", async (t) => { await page.goto ("http://localhost:7000/b", { "waitUntil": "networkidle0" })

const style = await page
  .evaluateHandle (getStyleElement, "(min-width: 768px)")
  .then ((el) => el._remoteObject.className)

const actual = await page.content ()

const expect = httpContent (
  /* eslint-disable-next-line max-len */
  `<style data-creator="@amory/style">.b{all:inherit}</style><style data-creator="@amory/style" media="(min-width: 768px)"></style>`
)

t.is (style, "HTMLStyleElement")
t.is (actual, expect)

} )

ava.serial ("given URL '/c', verify returned contents (1)", async (t) => { await page.goto ("http://localhost:7000/c", { "waitUntil": "networkidle0" })

const actual = await page.content ()

const expect = httpContent ( <style data-creator="@amory/style" media="(min-width: 768px)">.c{gap:1px}</style> )

t.is (actual, expect) })

ava.serial ( "given URL '/c', should create additional style element (2)", async (t) => { await page.goto ("http://localhost:7000/c", { "waitUntil": "networkidle0" })

const style = await page
  .evaluateHandle (getStyleElement)
  .then ((el) => el._remoteObject.className)

const actual = await page.content ()

const expect = httpContent (
  `<style data-creator="@amory/style" media="(min-width: 768px)">.c{gap:1px}</style><style data-creator="@amory/style"></style>`
)

t.is (style, "HTMLStyleElement")
t.is (actual, expect)

} )

ava.serial ( "given URL '/c', should re-use existing style element (3)", async (t) => { await page.goto ("http://localhost:7000/c", { "waitUntil": "networkidle0" })

const style = await page
  .evaluateHandle (getStyleElement, "(min-width: 768px)")
  .then ((el) => el._remoteObject.className)

const actual = await page.content ()

const expect = httpContent (
  `<style data-creator="@amory/style" media="(min-width: 768px)">.c{gap:1px}</style>`
)

t.is (style, "HTMLStyleElement")
t.is (actual, expect)

} ) / eslint-enable max-len / #+end_src #+HTML:

**** insertRule

#+HTML: #+begin_src js :tangle src/client/insert-rule.js import { canUseDom, getStyle, getStyleElement, isDef } from "../api/index.js"

/ istanbul ignore next /

export function insertRule (params = {}) { if (canUseDom) { const sheet = getStyleElement ().sheet const style = getStyle (params, true)

if (isDef (sheet) && style) {
  sheet.insertRule (style, sheet.cssRules.length)
}

}

return params } #+end_src #+HTML:

#+HTML: #+begin_src js :tangle src/client/insert-rule.test.js import ava from "ava" import { insertRule } from "./insert-rule.js"

ava ("given undefined arguments", (t) => { const actual = insertRule () const expect = {}

t.deepEqual (actual, expect) }) #+end_src #+HTML:

**** update

#+beginsrc js store.forEach (function (, media) { canUseDom && window.requestAnimationFrame (function () { const styles = getStyles (media)

  const style = elements.has (media)
    ? elements.get (media)
    : getStyleElement (media)

  style.innerHTML = styles
  elements.set (media, style)
})

})

return params #+end_src

#+HTML: #+begin_src js :tangle src/client/update.js import { canUseDom, insertRule } from "../api/index.js"

/ istanbul ignore next /

export function update (params = {}) { if (canUseDom && params.insertRule) { insertRule (params) }

return params } #+end_src #+HTML:

#+HTML: #+begin_src js :tangle src/client/update.test.js import ava from "ava" import { store } from "../store/store.js" import { update } from "./update.js"

ava ("1", (t) => { store.set ( "", new Map ([ [ '{"background-color":"#f00"}', { "block": { "background-color": "#f00" } , "emit": true, "identifier": "jt2a9", "input": { "background-color": "#f00" }, "media": "", "property": "backgroundColor", "selectors": [".jt2a9"] } ], [ '{"display":"flex"},{"display":"grid"}', { "block": { "display": "flex" }, { "display": "grid" }, "emit": true, "identifier": "dr7nz", "input": { "display": "flex", "grid" }, "media": "", "property": "display", "selectors": [".dr7nz"] } ] ]) )

const actual = update ()

const expect = {}

t.deepEqual (actual, expect) })

ava ("2", (t) => { store.set ( "(min-width: 768px)", new Map ([ [ '{"background-color":"#f00"}', { "block": { "background-color": "#f00" } , "emit": true, "identifier": "jt2a9", "input": { "background-color": "#f00" }, "media": "", "property": "backgroundColor", "selectors": [".jt2a9"] } ], [ '{"display":"flex"},{"display":"grid"}', { "block": { "display": "flex" }, { "display": "grid" }, "emit": true, "identifier": "dr7nz", "input": { "display": "flex", "grid" }, "media": "", "property": "display", "selectors": [".dr7nz"] } ] ]) )

const actual = update ()

const expect = {}

t.deepEqual (actual, expect) }) #+end_src #+HTML:

**** updateStyles

#+HTML: #+begin_src js :tangle src/client/update-styles.js import { debounce, getStyleElement, getStyles } from "../api/index.js"

/ istanbul ignore next /

export function updateStyles () { // export const updateStyles = debounce (function () { const style = getStyleElement () const styles = getStyles ()

style.innerHTML = styles

// }, 25) } #+end_src #+HTML:

#+HTML: #+begin_src js :tangle src/client/update-styles.test.js import ava from "ava"

// import { updateStyles } from "./update-styles.js"

ava.todo ("todo") #+end_src #+HTML:

*** Parse

**** parseFallbacks

#+HTML: #+begin_src js :tangle src/parse/parse-fallbacks.js import { isObj, kebabCase, merge, parseFontFace } from "../api/index.js"

/ eslint-disable max-lines-per-function / export function parseFallbacks (params = {}) { const value = params.value

if (Array.isArray (value)) { const property = params.property

let block = []
const styles = []

switch (property) {
  case "backgroundImage":
    block = [
      {
        [kebabCase (property)]: value.join (",")
      }
    ]
    break
  case "fontFamily":
    block = [
      {
        "font-family": value
          .reduce (function (fonts, font) {
            if (isObj (font)) {
              const fontFace = parseFontFace ({
                "property": property,
                "value": font
              }).shift ()

              styles.push (fontFace)

              return fonts.concat (fontFace.value)
            }

            return fonts.concat (font)
          }, [])
          .join (",")
      }
    ]
    break
  default:
    block = value.map (function (fallback) {
      return { [kebabCase (property)]: fallback }
    })
    break
}

return styles.concat (
  merge (
    params,
    {
      "block": null
    },
    {
      block
    }
  )
)

}

return params } / eslint-enable max-lines-per-function / #+end_src #+HTML:

#+HTML: #+begin_src js :tangle src/parse/parse-fallbacks.test.js import ava from "ava" import { parseFallbacks } from "./parse-fallbacks.js"

ava ("given undefined arguments", (t) => { const actual = parseFallbacks ()

const expect = {}

t.deepEqual (actual, expect) })

ava ( "given an object with backgroundImage property and array value (1)", (t) => { const actual = parseFallbacks ({ "property": "backgroundImage", "value": "url(https://mdn.mozillademos.org/files/11305/firefox.png)", "url(https://mdn.mozillademos.org/files/11307/bubbles.png)", "linear-gradient(to right, rgba(30, 75, 115, 1)", "rgba(255, 255, 255, 0))" })

const expect = [
  {
    "block": [
      {
        "background-image":
          "url(https://mdn.mozillademos.org/files/11305/firefox.png),url(https://mdn.mozillademos.org/files/11307/bubbles.png),linear-gradient(to right, rgba(30, 75, 115, 1),rgba(255, 255, 255, 0))"
      }
    ],
    "property": "backgroundImage",
    "value": [
      "url(https://mdn.mozillademos.org/files/11305/firefox.png)",
      "url(https://mdn.mozillademos.org/files/11307/bubbles.png)",
      "linear-gradient(to right, rgba(30, 75, 115, 1)",
      "rgba(255, 255, 255, 0))"
    ]
  }
]

t.deepEqual (actual, expect)

} )

/ eslint-disable max-lines-per-function / ava ("given an object with fontFamily property and array value (1)", (t) => { const actual = parseFallbacks ({ "property": "fontFamily", "value": [ { "fontFamily": "Avenir", "src": "url('/fonts/avenir.woff') format('woff')" }, "Helvetica", "Arial", {

2021.9.25

3 years ago

2021.9.20

3 years ago

2020.7.2

4 years ago

2020.7.1

4 years ago

2020.5.6-0

4 years ago

2020.5.5-0

4 years ago

2020.4.20-0

4 years ago

2020.4.17-0

4 years ago

2020.4.16-1

4 years ago

2020.4.16-0

4 years ago

2020.2.1-0

4 years ago

2020.1.9-0

4 years ago

2019.12.31-1

4 years ago

2019.12.31-0

4 years ago

2019.9.20-1

5 years ago

2019.9.20-0

5 years ago

2019.8.5-0

5 years ago

2019.7.18-0

5 years ago

2019.7.10-0

5 years ago

2019.6.13-1

5 years ago

2019.6.13-0

5 years ago

2019.5.31-0

5 years ago

2019.5.28-1

5 years ago

2019.5.28-0

5 years ago

2019.5.26-2

5 years ago

2019.5.26-1

5 years ago

2019.5.26-0

5 years ago

2019.5.24-2

5 years ago

2019.5.24-1

5 years ago

2019.5.24-0

5 years ago

2019.5.23-1

5 years ago

2019.5.23-0

5 years ago

2019.5.21-1

5 years ago

2019.5.21-0

5 years ago

2018.11.12-1

6 years ago

2018.11.12-0

6 years ago

2018.11.9-2

6 years ago

2018.11.9-1

6 years ago

2018.11.9-0

6 years ago

2018.11.7-11

6 years ago

2018.11.7-10

6 years ago

2018.11.7-9

6 years ago

2018.11.7-8

6 years ago

2018.11.7-7

6 years ago

2018.11.7-6

6 years ago

2018.11.7-5

6 years ago

2018.11.7-4

6 years ago

2018.11.7-3

6 years ago

2018.11.7-2

6 years ago

2018.11.7-1

6 years ago

2018.11.7-0

6 years ago

2018.11.5-6

6 years ago

2018.11.5-5

6 years ago

2018.11.5-4

6 years ago

2018.11.5-3

6 years ago

2018.11.5-2

6 years ago

2018.11.5-1

6 years ago

2018.11.5-0

6 years ago

2018.11.1-3

6 years ago

2018.11.1-2

6 years ago

2018.11.1-1

6 years ago

2018.11.1-0

6 years ago

2018.10.26-1

6 years ago

2018.10.26-0

6 years ago

2018.10.8-2

6 years ago

2018.10.6-0

6 years ago

2018.9.25-0

6 years ago

2018.9.10-0

6 years ago