summaryrefslogtreecommitdiff
path: root/stylis.js/src
diff options
context:
space:
mode:
Diffstat (limited to 'stylis.js/src')
-rw-r--r--stylis.js/src/Enum.js20
-rw-r--r--stylis.js/src/Middleware.js107
-rw-r--r--stylis.js/src/Parser.js174
-rw-r--r--stylis.js/src/Prefixer.js114
-rw-r--r--stylis.js/src/Serializer.js34
-rw-r--r--stylis.js/src/Tokenizer.js218
-rw-r--r--stylis.js/src/Utility.js109
7 files changed, 776 insertions, 0 deletions
diff --git a/stylis.js/src/Enum.js b/stylis.js/src/Enum.js
new file mode 100644
index 0000000..0d611ab
--- /dev/null
+++ b/stylis.js/src/Enum.js
@@ -0,0 +1,20 @@
+export var MS = '-ms-'
+export var MOZ = '-moz-'
+export var WEBKIT = '-webkit-'
+
+export var COMMENT = 'comm'
+export var RULESET = 'rule'
+export var DECLARATION = 'decl'
+
+export var PAGE = '@page'
+export var MEDIA = '@media'
+export var IMPORT = '@import'
+export var CHARSET = '@charset'
+export var VIEWPORT = '@viewport'
+export var SUPPORTS = '@supports'
+export var DOCUMENT = '@document'
+export var NAMESPACE = '@namespace'
+export var KEYFRAMES = '@keyframes'
+export var FONT_FACE = '@font-face'
+export var COUNTER_STYLE = '@counter-style'
+export var FONT_FEATURE_VALUES = '@font-feature-values'
diff --git a/stylis.js/src/Middleware.js b/stylis.js/src/Middleware.js
new file mode 100644
index 0000000..cec5f62
--- /dev/null
+++ b/stylis.js/src/Middleware.js
@@ -0,0 +1,107 @@
+import {MS, MOZ, WEBKIT, RULESET, KEYFRAMES, DECLARATION} from './Enum.js'
+import {match, charat, substr, strlen, sizeof, replace, combine} from './Utility.js'
+import {copy, tokenize} from './Tokenizer.js'
+import {serialize} from './Serializer.js'
+import {prefix} from './Prefixer.js'
+
+/**
+ * @param {function[]} collection
+ * @return {function}
+ */
+export function middleware (collection) {
+ var length = sizeof(collection)
+
+ return function (element, index, children, callback) {
+ var output = ''
+
+ for (var i = 0; i < length; i++)
+ output += collection[i](element, index, children, callback) || ''
+
+ return output
+ }
+}
+
+/**
+ * @param {function} callback
+ * @return {function}
+ */
+export function rulesheet (callback) {
+ return function (element) {
+ if (!element.root)
+ if (element = element.return)
+ callback(element)
+ }
+}
+
+/**
+ * @param {object} element
+ * @param {number} index
+ * @param {object[]} children
+ * @param {function} callback
+ */
+export function prefixer (element, index, children, callback) {
+ if (!element.return)
+ switch (element.type) {
+ case DECLARATION: element.return = prefix(element.value, element.length)
+ break
+ case KEYFRAMES:
+ return serialize([copy(replace(element.value, '@', '@' + WEBKIT), element, '')], callback)
+ case RULESET:
+ if (element.length)
+ return combine(element.props, function (value) {
+ switch (match(value, /(::plac\w+|:read-\w+)/)) {
+ // :read-(only|write)
+ case ':read-only': case ':read-write':
+ return serialize([copy(replace(value, /:(read-\w+)/, ':' + MOZ + '$1'), element, '')], callback)
+ // :placeholder
+ case '::placeholder':
+ return serialize([
+ copy(replace(value, /:(plac\w+)/, ':' + WEBKIT + 'input-$1'), element, ''),
+ copy(replace(value, /:(plac\w+)/, ':' + MOZ + '$1'), element, ''),
+ copy(replace(value, /:(plac\w+)/, MS + 'input-$1'), element, '')
+ ], callback)
+ }
+
+ return ''
+ })
+ }
+}
+
+/**
+ * @param {object} element
+ * @param {number} index
+ * @param {object[]} children
+ */
+export function namespace (element) {
+ switch (element.type) {
+ case RULESET:
+ element.props = element.props.map(function (value) {
+ return combine(tokenize(value), function (value, index, children) {
+ switch (charat(value, 0)) {
+ // \f
+ case 12:
+ return substr(value, 1, strlen(value))
+ // \0 ( + > ~
+ case 0: case 40: case 43: case 62: case 126:
+ return value
+ // :
+ case 58:
+ if (children[index + 1] === 'global')
+ children[index + 1] = '', children[index + 2] = '\f' + substr(children[index + 2], index = 1, -1)
+ // \s
+ case 32:
+ return index === 1 ? '' : value
+ default:
+ switch (index) {
+ case 0: element = value
+ return sizeof(children) > 1 ? '' : value
+ case index = sizeof(children) - 1: case 2:
+ return index === 2 ? value + element + element : value + element
+ default:
+ return value
+ }
+ }
+ })
+ })
+ }
+}
diff --git a/stylis.js/src/Parser.js b/stylis.js/src/Parser.js
new file mode 100644
index 0000000..4089514
--- /dev/null
+++ b/stylis.js/src/Parser.js
@@ -0,0 +1,174 @@
+import {COMMENT, RULESET, DECLARATION} from './Enum.js'
+import {abs, trim, from, sizeof, strlen, substr, append, replace} from './Utility.js'
+import {node, char, next, peek, caret, alloc, dealloc, delimit, whitespace, identifier, commenter} from './Tokenizer.js'
+
+/**
+ * @param {string} value
+ * @return {object[]}
+ */
+export function compile (value) {
+ return dealloc(parse('', null, null, null, [''], value = alloc(value), 0, [0], value))
+}
+
+/**
+ * @param {string} value
+ * @param {object} root
+ * @param {object?} parent
+ * @param {string[]} rule
+ * @param {string[]} rules
+ * @param {string[]} rulesets
+ * @param {number[]} pseudo
+ * @param {number[]} points
+ * @param {string[]} declarations
+ * @return {object}
+ */
+export function parse (value, root, parent, rule, rules, rulesets, pseudo, points, declarations) {
+ var index = 0
+ var offset = 0
+ var length = pseudo
+ var atrule = 0
+ var property = 0
+ var previous = 0
+ var variable = 1
+ var scanning = 1
+ var ampersand = 1
+ var character = 0
+ var type = ''
+ var props = rules
+ var children = rulesets
+ var reference = rule
+ var characters = type
+
+ while (scanning)
+ switch (previous = character, character = next()) {
+ // " ' [ (
+ case 34: case 39: case 91: case 40:
+ characters += delimit(character)
+ break
+ // \t \n \r \s
+ case 9: case 10: case 13: case 32:
+ characters += whitespace(previous)
+ break
+ // /
+ case 47:
+ switch (peek()) {
+ case 42: case 47:
+ append(comment(commenter(next(), caret()), root, parent), declarations)
+ break
+ default:
+ characters += '/'
+ }
+ break
+ // {
+ case 123 * variable:
+ points[index++] = strlen(characters) * ampersand
+ // } ; \0
+ case 125 * variable: case 59: case 0:
+ switch (character) {
+ // \0 }
+ case 0: case 125: scanning = 0
+ // ;
+ case 59 + offset:
+ if (property > 0)
+ append(property > 32 ? declaration(characters + ';', rule, parent, length - 1) : declaration(replace(characters, ' ', '') + ';', rule, parent, length - 2), declarations)
+ break
+ // @ ;
+ case 59: characters += ';'
+ // { rule/at-rule
+ default:
+ append(reference = ruleset(characters, root, parent, index, offset, rules, points, type, props = [], children = [], length), rulesets)
+
+ if (character === 123)
+ if (offset === 0)
+ parse(characters, root, reference, reference, props, rulesets, length, points, children)
+ else
+ switch (atrule) {
+ // d m s
+ case 100: case 109: case 115:
+ parse(value, reference, reference, rule && append(ruleset(value, reference, reference, 0, 0, rules, points, type, rules, props = [], length), children), rules, children, length, points, rule ? props : children)
+ break
+ default:
+ parse(characters, reference, reference, reference, [''], children, length, points, children)
+ }
+ }
+
+ index = offset = property = 0, variable = ampersand = 1, type = characters = '', length = pseudo
+ break
+ // :
+ case 58:
+ length = 1 + strlen(characters), property = previous
+ default:
+ switch (characters += from(character), character * variable) {
+ // &
+ case 38:
+ ampersand = offset > 0 ? 1 : (characters += '\f', -1)
+ break
+ // ,
+ case 44:
+ points[index++] = (strlen(characters) - 1) * ampersand, ampersand = 1
+ break
+ // @
+ case 64:
+ // -
+ if (peek() === 45)
+ characters += delimit(next())
+
+ atrule = peek(), offset = strlen(type = characters += identifier(caret())), character++
+ break
+ // -
+ case 45:
+ if (previous === 45 && strlen(characters) == 2)
+ variable = 0
+ }
+ }
+
+ return rulesets
+}
+
+/**
+ * @param {string} value
+ * @param {object} root
+ * @param {object?} parent
+ * @param {number} index
+ * @param {number} offset
+ * @param {string[]} rules
+ * @param {number[]} points
+ * @param {string} type
+ * @param {string[]} props
+ * @param {string[]} children
+ * @param {number} length
+ * @return {object}
+ */
+export function ruleset (value, root, parent, index, offset, rules, points, type, props, children, length) {
+ var post = offset - 1
+ var rule = offset === 0 ? rules : ['']
+ var size = sizeof(rule)
+
+ for (var i = 0, j = 0, k = 0; i < index; ++i)
+ for (var x = 0, y = substr(value, post + 1, post = abs(j = points[i])), z = value; x < size; ++x)
+ if (z = trim(j > 0 ? rule[x] + ' ' + y : replace(y, /&\f/g, rule[x])))
+ props[k++] = z
+
+ return node(value, root, parent, offset === 0 ? RULESET : type, props, children, length)
+}
+
+/**
+ * @param {number} value
+ * @param {object} root
+ * @param {object?} parent
+ * @return {object}
+ */
+export function comment (value, root, parent) {
+ return node(value, root, parent, COMMENT, from(char()), substr(value, 2, -2), 0)
+}
+
+/**
+ * @param {string} value
+ * @param {object} root
+ * @param {object?} parent
+ * @param {number} length
+ * @return {object}
+ */
+export function declaration (value, root, parent, length) {
+ return node(value, root, parent, DECLARATION, substr(value, 0, length), substr(value, length + 1, -1), length)
+}
diff --git a/stylis.js/src/Prefixer.js b/stylis.js/src/Prefixer.js
new file mode 100644
index 0000000..e156c21
--- /dev/null
+++ b/stylis.js/src/Prefixer.js
@@ -0,0 +1,114 @@
+import {MS, MOZ, WEBKIT} from './Enum.js'
+import {hash, charat, strlen, indexof, replace} from './Utility.js'
+
+/**
+ * @param {string} value
+ * @param {number} length
+ * @return {string}
+ */
+export function prefix (value, length) {
+ switch (hash(value, length)) {
+ // animation, animation-(delay|direction|duration|fill-mode|iteration-count|name|play-state|timing-function)
+ case 5737: case 4201: case 3177: case 3433: case 1641: case 4457: case 2921:
+ // text-decoration, filter, clip-path, backface-visibility, column, box-decoration-break
+ case 5572: case 6356: case 5844: case 3191: case 6645: case 3005:
+ // mask, mask-image, mask-(mode|clip|size), mask-(repeat|origin), mask-position, mask-composite,
+ case 6391: case 5879: case 5623: case 6135: case 4599: case 4855:
+ // background-clip, columns, column-(count|fill|gap|rule|rule-color|rule-style|rule-width|span|width)
+ case 4215: case 6389: case 5109: case 5365: case 5621: case 3829:
+ return WEBKIT + value + value
+ // appearance, user-select, transform, hyphens, text-size-adjust
+ case 5349: case 4246: case 4810: case 6968: case 2756:
+ return WEBKIT + value + MOZ + value + MS + value + value
+ // flex, flex-direction
+ case 6828: case 4268:
+ return WEBKIT + value + MS + value + value
+ // order
+ case 6165:
+ return WEBKIT + value + MS + 'flex-' + value + value
+ // align-items
+ case 5187:
+ return WEBKIT + value + replace(value, /(\w+).+(:[^]+)/, WEBKIT + 'box-$1$2' + MS + 'flex-$1$2') + value
+ // align-self
+ case 5443:
+ return WEBKIT + value + MS + 'flex-item-' + replace(value, /flex-|-self/, '') + value
+ // align-content
+ case 4675:
+ return WEBKIT + value + MS + 'flex-line-pack' + replace(value, /align-content|flex-|-self/, '') + value
+ // flex-shrink
+ case 5548:
+ return WEBKIT + value + MS + replace(value, 'shrink', 'negative') + value
+ // flex-basis
+ case 5292:
+ return WEBKIT + value + MS + replace(value, 'basis', 'preferred-size') + value
+ // flex-grow
+ case 6060:
+ return WEBKIT + 'box-' + replace(value, '-grow', '') + WEBKIT + value + MS + replace(value, 'grow', 'positive') + value
+ // transition
+ case 4554:
+ return WEBKIT + replace(value, /([^-])(transform)/g, '$1' + WEBKIT + '$2') + value
+ // cursor
+ case 6187:
+ return replace(replace(replace(value, /(zoom-|grab)/, WEBKIT + '$1'), /(image-set)/, WEBKIT + '$1'), value, '') + value
+ // background, background-image
+ case 5495: case 3959:
+ return replace(value, /(image-set\([^]*)/, WEBKIT + '$1' + '$`$1')
+ // justify-content
+ case 4968:
+ return replace(replace(value, /(.+:)(flex-)?(.*)/, WEBKIT + 'box-pack:$3' + MS + 'flex-pack:$3'), /s.+-b[^;]+/, 'justify') + WEBKIT + value + value
+ // (margin|padding)-inline-(start|end)
+ case 4095: case 3583: case 4068: case 2532:
+ return replace(value, /(.+)-inline(.+)/, WEBKIT + '$1$2') + value
+ // (min|max)?(width|height|inline-size|block-size)
+ case 8116: case 7059: case 5753: case 5535:
+ case 5445: case 5701: case 4933: case 4677:
+ case 5533: case 5789: case 5021: case 4765:
+ // stretch, max-content, min-content, fill-available
+ if (strlen(value) - 1 - length > 6)
+ switch (charat(value, length + 1)) {
+ // (m)ax-content, (m)in-content
+ case 109:
+ return replace(value, /(.+:)(.+)-([^]+)/, '$1' + WEBKIT + '$2-$3' + '$1' + MOZ + '$2-$3') + value
+ // (f)ill-available
+ case 102:
+ return replace(value, /(.+:)(.+)-([^]+)/, '$1' + WEBKIT + '$2-$3' + '$1' + MOZ + '$3') + value
+ // (s)tretch
+ case 115:
+ return prefix(replace(value, 'stretch', 'fill-available'), length) + value
+ }
+ break
+ // position: sticky
+ case 4949:
+ // (s)ticky?
+ if (charat(value, length + 1) !== 115)
+ break
+ // display: (flex|inline-flex|inline-box)
+ case 6444:
+ switch (charat(value, strlen(value) - 3 - (~indexof(value, '!important') && 10))) {
+ // stic(k)y, inline-b(o)x
+ case 107: case 111:
+ return replace(value, value, WEBKIT + value) + value
+ // (inline-)?fl(e)x
+ case 101:
+ return replace(value, /(.+:)([^;!]+)(;|!.+)?/, '$1' + WEBKIT + (charat(value, 14) === 45 ? 'inline-' : '') + 'box$3' + '$1' + WEBKIT + '$2$3' + '$1' + MS + '$2box$3') + value
+ }
+ break
+ // writing-mode
+ case 5936:
+ switch (charat(value, length + 11)) {
+ // vertical-l(r)
+ case 114:
+ return WEBKIT + value + MS + replace(value, /[svh]\w+-[tblr]{2}/, 'tb') + value
+ // vertical-r(l)
+ case 108:
+ return WEBKIT + value + MS + replace(value, /[svh]\w+-[tblr]{2}/, 'tb-rl') + value
+ // horizontal(-)tb
+ case 45:
+ return WEBKIT + value + MS + replace(value, /[svh]\w+-[tblr]{2}/, 'lr') + value
+ }
+
+ return WEBKIT + value + MS + value + value
+ }
+
+ return value
+}
diff --git a/stylis.js/src/Serializer.js b/stylis.js/src/Serializer.js
new file mode 100644
index 0000000..c82f0b4
--- /dev/null
+++ b/stylis.js/src/Serializer.js
@@ -0,0 +1,34 @@
+import {IMPORT, COMMENT, RULESET, DECLARATION} from './Enum.js'
+import {strlen, sizeof} from './Utility.js'
+
+/**
+ * @param {object[]} children
+ * @param {function} callback
+ * @return {string}
+ */
+export function serialize (children, callback) {
+ var output = ''
+ var length = sizeof(children)
+
+ for (var i = 0; i < length; i++)
+ output += callback(children[i], i, children, callback) || ''
+
+ return output
+}
+
+/**
+ * @param {object} element
+ * @param {number} index
+ * @param {object[]} children
+ * @param {function} callback
+ * @return {string}
+ */
+export function stringify (element, index, children, callback) {
+ switch (element.type) {
+ case IMPORT: case DECLARATION: return element.return = element.return || element.value
+ case COMMENT: return ''
+ case RULESET: element.value = element.props.join(',')
+ }
+
+ return strlen(children = serialize(element.children, callback)) ? element.return = element.value + '{' + children + '}' : ''
+}
diff --git a/stylis.js/src/Tokenizer.js b/stylis.js/src/Tokenizer.js
new file mode 100644
index 0000000..d14c136
--- /dev/null
+++ b/stylis.js/src/Tokenizer.js
@@ -0,0 +1,218 @@
+import {from, trim, charat, strlen, substr, append} from './Utility.js'
+
+export var line = 1
+export var column = 1
+export var length = 0
+export var position = 0
+export var character = 0
+export var characters = ''
+
+/**
+ * @param {string} value
+ * @param {object} root
+ * @param {object?} parent
+ * @param {string} type
+ * @param {string[]} props
+ * @param {object[]} children
+ * @param {number} length
+ */
+export function node (value, root, parent, type, props, children, length) {
+ return {value: value, root: root, parent: parent, type: type, props: props, children: children, line: line, column: column, length: length, return: ''}
+}
+
+/**
+ * @param {string} value
+ * @param {object} root
+ * @param {string} type
+ */
+export function copy (value, root, type) {
+ return node(value, root.root, root.parent, type, root.props, root.children, 0)
+}
+
+/**
+ * @return {number}
+ */
+export function char () {
+ return character
+}
+
+/**
+ * @return {number}
+ */
+export function next () {
+ character = position < length ? charat(characters, position++) : 0
+
+ if (column++, character === 10)
+ column = 1, line++
+
+ return character
+}
+
+/**
+ * @return {number}
+ */
+export function peek () {
+ return charat(characters, position)
+}
+
+/**
+ * @return {number}
+ */
+export function caret () {
+ return position
+}
+
+/**
+ * @param {number} begin
+ * @param {number} end
+ * @return {string}
+ */
+export function slice (begin, end) {
+ return substr(characters, begin, end)
+}
+
+/**
+ * @param {number} type
+ * @return {number}
+ */
+export function token (type) {
+ switch (type) {
+ // \0 \t \n \r \s whitespace token
+ case 0: case 9: case 10: case 13: case 32:
+ return 5
+ // ! + , / > @ ~ isolate token
+ case 33: case 43: case 44: case 47: case 62: case 64: case 126:
+ // ; { } / breakpoint token
+ case 59: case 123: case 125:
+ return 4
+ // : accompanied token
+ case 58:
+ return 3
+ // " ' ( [ opening delimit token
+ case 34: case 39: case 40: case 91:
+ return 2
+ // ) ] closing delimit token
+ case 41: case 93:
+ return 1
+ }
+
+ return 0
+}
+
+/**
+ * @param {string} value
+ * @return {any[]}
+ */
+export function alloc (value) {
+ return line = column = 1, length = strlen(characters = value), position = 0, []
+}
+
+/**
+ * @param {any} value
+ * @return {any}
+ */
+export function dealloc (value) {
+ return characters = '', value
+}
+
+/**
+ * @param {number} type
+ * @return {string}
+ */
+export function delimit (type) {
+ return trim(slice(position - 1, delimiter(type === 91 ? type + 2 : type === 40 ? type + 1 : type)))
+}
+
+/**
+ * @param {string} value
+ * @return {string[]}
+ */
+export function tokenize (value) {
+ return dealloc(tokenizer(alloc(value)))
+}
+
+/**
+ * @param {number} type
+ * @return {string}
+ */
+export function whitespace (type) {
+ while (character = peek())
+ if (character < 33)
+ next()
+ else
+ break
+
+ return token(type) > 2 || token(character) > 3 ? '' : ' '
+}
+
+/**
+ * @param {string[]} children
+ * @return {string[]}
+ */
+export function tokenizer (children) {
+ while (next())
+ switch (token(character)) {
+ case 0: append(identifier(position - 1), children)
+ break
+ case 2: append(delimit(character), children)
+ break
+ default: append(from(character), children)
+ }
+
+ return children
+}
+
+/**
+ * @param {number} type
+ * @return {number}
+ */
+export function delimiter (type) {
+ while (next())
+ switch (character) {
+ // ] ) " '
+ case type:
+ return position
+ // " '
+ case 34: case 39:
+ return delimiter(type === 34 || type === 39 ? type : character)
+ // (
+ case 40:
+ if (type === 41)
+ delimiter(type)
+ break
+ // \
+ case 92:
+ next()
+ break
+ }
+
+ return position
+}
+
+/**
+ * @param {number} type
+ * @param {number} index
+ * @return {number}
+ */
+export function commenter (type, index) {
+ while (next())
+ // //
+ if (type + character === 47 + 10)
+ break
+ // /*
+ else if (type + character === 42 + 42 && peek() === 47)
+ break
+
+ return '/*' + slice(index, position - 1) + '*' + from(type === 47 ? type : next())
+}
+
+/**
+ * @param {number} index
+ * @return {string}
+ */
+export function identifier (index) {
+ while (!token(peek()))
+ next()
+
+ return slice(index, position)
+}
diff --git a/stylis.js/src/Utility.js b/stylis.js/src/Utility.js
new file mode 100644
index 0000000..f29590e
--- /dev/null
+++ b/stylis.js/src/Utility.js
@@ -0,0 +1,109 @@
+/**
+ * @param {number}
+ * @return {number}
+ */
+export var abs = Math.abs
+
+/**
+ * @param {number}
+ * @return {string}
+ */
+export var from = String.fromCharCode
+
+/**
+ * @param {string} value
+ * @param {number} length
+ * @return {number}
+ */
+export function hash (value, length) {
+ return (((((((length << 2) ^ charat(value, 0)) << 2) ^ charat(value, 1)) << 2) ^ charat(value, 2)) << 2) ^ charat(value, 3)
+}
+
+/**
+ * @param {string} value
+ * @return {string}
+ */
+export function trim (value) {
+ return value.trim()
+}
+
+/**
+ * @param {string} value
+ * @param {RegExp} pattern
+ * @return {string?}
+ */
+export function match (value, pattern) {
+ return (value = pattern.exec(value)) ? value[0] : value
+}
+
+/**
+ * @param {string} value
+ * @param {(string|RegExp)} pattern
+ * @param {string} replacement
+ * @return {string}
+ */
+export function replace (value, pattern, replacement) {
+ return value.replace(pattern, replacement)
+}
+
+/**
+ * @param {string} value
+ * @param {string} value
+ * @return {number}
+ */
+export function indexof (value, search) {
+ return value.indexOf(search)
+}
+
+/**
+ * @param {string} value
+ * @param {number} index
+ * @return {number}
+ */
+export function charat (value, index) {
+ return value.charCodeAt(index) | 0
+}
+
+/**
+ * @param {string} value
+ * @param {number} begin
+ * @param {number} end
+ * @return {string}
+ */
+export function substr (value, begin, end) {
+ return value.slice(begin, end)
+}
+
+/**
+ * @param {string} value
+ * @return {number}
+ */
+export function strlen (value) {
+ return value.length
+}
+
+/**
+ * @param {any[]} value
+ * @return {number}
+ */
+export function sizeof (value) {
+ return value.length
+}
+
+/**
+ * @param {any} value
+ * @param {any[]} array
+ * @return {any}
+ */
+export function append (value, array) {
+ return array.push(value), value
+}
+
+/**
+ * @param {string[]} array
+ * @param {function} callback
+ * @return {string}
+ */
+export function combine (array, callback) {
+ return array.map(callback).join('')
+}