lib/nano_css.rb in isomorfeus-preact-10.9.0 vs lib/nano_css.rb in isomorfeus-preact-22.9.0.rc1

- old
+ new

@@ -1,481 +1,298 @@ -module NanoCSS - %x{ - var KEBAB_REGEX = /[A-Z]/g; - var on_browser = #{on_browser?}; - var in_dev = #{Isomorfeus.development?}; +class NanoCSS + KEBAB_REGEX = /[A-Z]/ + UNITLESS_NUMBER_PROPS = %w[animation-iteration-count border-image-outset border-image-slice + border-image-width box-flex box-flex-group box-ordinal-group column-count columns flex + flex-grow flex-positive flex-shrink flex-negative flex-order grid-row grid-row-end + grid-row-span grid-row-start grid-column grid-column-end grid-column-span grid-column-start + font-weight line-clamp line-height opacity order orphans tabSize widows z-index zoom + fill-opacity flood-opacity stop-opacity stroke-dasharray stroke-dashoffset stroke-miterlimit + stroke-opacity stroke-width] # from fill-opacity onwards is for SVG - function hash(str) { - var h = 5381, i = str.length; - while (i) h = (h * 33) ^ str.charCodeAt(--i); - return '_' + (h >>> 0).toString(36); - }; + class << self + if RUBY_ENGINE == 'opal' + def instance + @instance + end - self.create = function (config) { - config = config || {}; - var assign = config.assign || Object.assign; - var client = typeof window === 'object'; + def instance=(i) + @instance = i + end + else + def instance + i = Thread.current[:@_isomorfeus_preact_nanocss_instance] + return i if i + @global_instance + end - // Check if we are really in browser environment. - if (on_browser && in_dev) { - if (client) { - if ((typeof document !== 'object') || !document.getElementsByTagName('HTML')) { - console.error('nano-css detected browser environment because of "window" global, but "document" global seems to be defective.'); - } - } - } + def instance=(i) + @global_instance = i unless @global_instance + Thread.current[:@_isomorfeus_preact_nanocss_instance] = i + end - var renderer = assign({ - raw: '', - pfx: '_', - client: client, - assign: assign, - stringify: JSON.stringify, - kebab: function (prop) { - return prop.replace(KEBAB_REGEX, '-$&').toLowerCase(); - }, - decl: function (key, value) { - key = renderer.kebab(key); - return key + ':' + value + ';'; - }, - hash: function (obj) { return hash(renderer.stringify(obj)); }, - selector: function (parent, selector) { - return parent + (selector[0] === ':' ? '' : ' ') + selector; - }, - putRaw: function (rawCssRule) { renderer.raw += rawCssRule; } - }, config); + def global_instance + @global_instance + end + end + end - if (renderer.client) { - if (!renderer.sh) { document.head.appendChild(renderer.sh = document.createElement('style')); } + @@unitless_css_properties = nil - if (in_dev) { - renderer.sh.setAttribute('data-nano-css-dev', ''); + attr_reader :renderer - // Test style sheet used in DEV mode to test if .insetRule() would throw. - renderer.shTest = document.createElement('style'); - renderer.shTest.setAttribute('data-nano-css-dev-tests', ''); - document.head.appendChild(renderer.shTest); - } + def initialize(config = nil, given_renderer: nil) + config = {} unless config + if RUBY_ENGINE == 'opal' + @client = `typeof window === "object"` + else + @client = false + end - renderer.putRaw = function (rawCssRule) { - // .insertRule() is faster than .appendChild(), that's why we use it in PROD. - // But CSS injected using .insertRule() is not displayed in Chrome Devtools - var sheet = renderer.sh.sheet; - if (!in_dev) { - // Unknown pseudo-selectors will throw, this try/catch swallows all errors. - try { sheet.insertRule(rawCssRule, sheet.cssRules.length); } - catch (error) {} - } else { - // Test if .insertRule() works in dev mode. Unknown pseudo-selectors will throw when - try { - var testSheet = renderer.shTest.sheet; - testSheet.insertRule(rawCssRule, testSheet.cssRules.length); - testSheet.deleteRule(testSheet.cssRules.length - 1); - } catch (error) { - if (config.verbose) { - console.error(error); - } - } - sheet.insertRule(rawCssRule, sheet.cssRules.length); - } - }; - } + if given_renderer + @renderer = given_renderer + else + @renderer = { raw: '', pfx: '_', hydrate_force_put: false, prefixes: ['-webkit-', '-moz-', '-o-', ''] } + @renderer.merge!(config) + end - renderer.put = function (selector, decls, atrule) { - var str = ''; - var prop, value; - var postponed = []; + @hydrated = {} - for (prop in decls) { - value = decls[prop]; + if @client + unless @renderer.key?(:sh) + e = `document.createElement('style')` + @renderer[:sh] = e + `document.head.appendChild(e)` + end - if ((value instanceof Object) && !(value instanceof Array)) { - postponed.push(prop); - } else { - if (in_dev && !renderer.sourcemaps) { - str += ' ' + renderer.decl(prop, value, selector, atrule) + '\n'; - } else { - str += renderer.decl(prop, value, selector, atrule); - } - } - } + unless @renderer.key?(:kh) + e = `document.createElement('style')` + @renderer[:ksh] = e + `document.head.appendChild(e)` + end - if (str) { - if (in_dev && !renderer.sourcemaps) { - str = '\n' + selector + ' {\n' + str + '}\n'; - } else { - str = selector + '{' + str + '}'; - } - renderer.putRaw(atrule ? atrule + '{' + str + '}' : str); - } + hydrate(@renderer[:sh]) + end - for (var i = 0; i < postponed.length; i++) { - prop = postponed[i]; + unless @@unitless_css_properties + @@unitless_css_properties = {} + UNITLESS_NUMBER_PROPS.each do |prop| + @@unitless_css_properties[prop] = 1 + @@unitless_css_properties['-webkit-' + prop] = 1 + @@unitless_css_properties['-ms-' + prop] = 1 + @@unitless_css_properties['-moz-' + prop] = 1 + @@unitless_css_properties['-o-' + prop] = 1 + end + end - if (prop[0] === '@' && prop !== '@font-face') { - renderer.putAt(selector, decls[prop], prop); - } else { - renderer.put(renderer.selector(selector, prop), decls[prop], atrule); - } - } - }; + unless given_renderer + put('', { '@keyframes fadein' => { from: { opacity: 0 }, to: { opacity: 1 }}, + '.fade_in' => { animation: 'fadein .4s linear' }}) + put('', { '@keyframes fadeout' => { from: { opacity: 1 }, to: { opacity: 0 }}, + '.fade_out' => { animation: 'fadeout .3s linear', 'animation-fill-mode' => 'forwards' }}) + end + end - renderer.putAt = renderer.put; + def decl(key, value) + key = kebab(key) - return renderer; - }; - } + if value.is_a?(Numeric) && !@@unitless_css_properties.key?(key) + "#{key}:#{value}px;" + else + "#{key}:#{value};" + end + end - # addons + def hash(obj) + hash_str(JSON.dump(obj)) + end - %x{ - self.rule = function (renderer) { - if (in_dev) { - renderer.rule_blocks = {}; - } + def hash_str(str) + h = 5381 + str.each_codepoint do |cp| + h = (h * 33) ^ cp + end + '_' + h.abs.to_s(36) + end - renderer.delete_from_rule_blocks = function (rule_name) { - rule_name = rule_name + '-'; - for(const rule in renderer.rule_blocks) { - if (rule.startsWith(rule_name)) { delete renderer.rule_blocks[rule]; } - } - } + def kebab(prop) + prop.to_s.gsub(KEBAB_REGEX, '-$&').downcase.to_sym + end - renderer.rule = function (css, block) { - // Warn user if CSS selectors clash. - if (in_dev) { - if (block) { - if (typeof block !== 'string') { - throw new TypeError( - 'nano-css block name must be a string. ' + - 'For example, use nano.rule({color: "red"}, "RedText").' - ); - } + def put(css_selector, decls, atrule = nil) + return if @client && !@renderer[:hydrate_force_put] && @hydrated.key?(css_selector) - if (renderer.rule_blocks[block]) { - console.error('nano-css block name "' + block + '" used more than once.'); - } + str = '' + postponed = [] - renderer.rule_blocks[block] = 1; - } - } + decls.each do |prop, value| + if value.is_a?(Hash) && !value.is_a?(Array) + postponed.push(prop) + else + str += decl(prop, value) + end + end - block = block || renderer.hash(css); - block = renderer.pfx + block; - renderer.put('.' + block, css); + unless str.empty? + str = css_selector + '{' + str + '}' + put_raw(atrule ? atrule + '{' + str + '}' : str) + end - return ' ' + block; - }; - }; - } + postponed.each do |prop| + if prop[0] === '@' && prop != '@font-face' + put_at(css_selector, decls[prop], prop) + else + put(selector(css_selector, prop), decls[prop], atrule) + end + end + end - %x{ - self.sheet = function (renderer) { - renderer.delete_from_sheet = function(rule_name) { - let selector_rule_name = "._" + rule_name + "-"; - if (renderer.sh && renderer.sh.sheet) { - let sheet = renderer.sh.sheet; - let rules = sheet.cssRules; - for (let i=rules.length-1;i>=0;i--) { - let rule = rules.item(i); - if (rule.cssText.includes(selector_rule_name)) { - sheet.deleteRule(i); - } - } - } - } + def put_at(_, keyframes, prelude) + if prelude[1] == 'k' + str = '' + keyframes.each do |keyframe, decls| + str_decls = '' + decls.each do |prop, value| + str_decls += decl(prop, value) + end + str += "#{keyframe}{#{str_decls}}" + end - renderer.sheet = function (map, block) { - var result = {}; + @renderer[:prefixes].each do |prefix| + raw_key_frames = prelude.sub('@keyframes', "@#{prefix}keyframes") + "{#{str}}" + if @client + ksh = @renderer[:ksh] + `ksh.appendChild(document.createTextNode(raw_key_frames))` + else + put_raw(raw_key_frames) + end + end - if (!block) { - block = renderer.hash(map); - } + return + end + put(nil, keyframes, prelude) + end - var onElementModifier = function (elementModifier) { - var styles = map[elementModifier]; + def put_raw(raw_css_rule) + @client ? put_raw_client(raw_css_rule) : put_raw_ssr(raw_css_rule) + end - if (in_dev && renderer.sourcemaps) { - // In dev mode emit CSS immediately to generate sourcemaps. - result[elementModifier] = renderer.rule(styles, block + '-' + elementModifier); - } else { - Object.defineProperty(result, elementModifier, { - configurable: true, - enumerable: true, - get: function () { - var classNames = renderer.rule(styles, block + '-' + elementModifier); + def put_raw_ssr(raw_css_rule) + @renderer[:raw] << raw_css_rule + end - Object.defineProperty(result, elementModifier, { - value: classNames, - enumerable: true - }); + def put_raw_client(raw_css_rule) + # .insertRule() is faster than .appendChild(), that's why we use it in PROD. + # But CSS injected using .insertRule() is not displayed in Chrome Devtools + sheet = @renderer[:sh].JS[:sheet] + # Unknown pseudo-selectors will throw, this try/catch swallows all errors. + `sheet.insertRule(raw_css_rule, sheet.cssRules.length)` rescue nil + end - return classNames; - }, - }); - } - }; + # addons - for (var elementModifier in map) { - onElementModifier(elementModifier); - } + # rule - return result; - }; - }; - } + def rule(css, block = nil) + block = block || hash(css) + block = @renderer[:pfx] + block + put('.' + block, css) - %x{ - self.nesting = function (renderer) { - renderer.selector = function (parentSelectors, selector) { - var parents = parentSelectors.split(','); - var result = []; - var selectors = selector.split(','); - var len1 = parents.length; - var len2 = selectors.length; - var i, j, sel, pos, parent, replacedSelector; + ' ' + block + end - for (i = 0; i < len2; i++) { - sel = selectors[i]; - pos = sel.indexOf('&'); + # sheet - if (pos > -1) { - for (j = 0; j < len1; j++) { - parent = parents[j]; - replacedSelector = sel.replace(/&/g, parent); - result.push(replacedSelector); - } - } else { - for (j = 0; j < len1; j++) { - parent = parents[j]; - - if (parent) { - result.push(parent + ' ' + sel); - } else { - result.push(sel); - } - } + def delete_from_sheet(rule_name) + selector_rule_name = "._" + rule_name + "-" + if renderer[:sh] && renderer[:sh].JS[:sheet] + sheet = renderer[:sh].JS[:sheet] + css_rules = sheet.JS[:cssRules] + %x{ + let i = 0; + for(i=0; i<css_rules.length; i++) { + if (css_rules[i].cssText.includes(selector_rule_name)) { + sheet.deleteRule(i); } } - - return result.join(','); - }; - }; - } - - %x{ - self.hydrate = function (renderer) { - var hydrated = {}; - renderer.hydrate_force_put = false; - renderer.hydrate = function (sh) { - var cssRules = sh.cssRules || sh.sheet.cssRules; - - for (var i = 0; i < cssRules.length; i++) - hydrated[cssRules[i].selectorText] = 1; - }; - - if (renderer.client) { - if (renderer.sh) renderer.hydrate(renderer.sh); - - var put = renderer.put; - - renderer.put = function (selector, css) { - if (!renderer.hydrate_force_put && selector in hydrated) return; - - put(selector, css); - }; } - }; - } + end + end - %x{ - var UNITLESS_NUMBER_PROPS = [ - 'animation-iteration-count', - 'border-image-outset', - 'border-image-slice', - 'border-image-width', - 'box-flex', - 'box-flex-group', - 'box-ordinal-group', - 'column-count', - 'columns', - 'flex', - 'flex-grow', - 'flex-positive', - 'flex-shrink', - 'flex-negative', - 'flex-order', - 'grid-row', - 'grid-row-end', - 'grid-row-span', - 'grid-row-start', - 'grid-column', - 'grid-column-end', - 'grid-column-span', - 'grid-column-start', - 'font-weight', - 'line-clamp', - 'line-height', - 'opacity', - 'order', - 'orphans', - 'tabSize', - 'widows', - 'z-index', - 'zoom', + def on_element_modifier(element_modifier, map, block, result) + result[element_modifier] = rule(map[element_modifier], "#{block}-#{element_modifier}") + end - // SVG-related properties - 'fill-opacity', - 'flood-opacity', - 'stop-opacity', - 'stroke-dasharray', - 'stroke-dashoffset', - 'stroke-miterlimit', - 'stroke-opacity', - 'stroke-width', - ]; + def sheet(map, block = nil) + result = {} + block = hash(map) unless block + map.each_key do |element_modifier| + on_element_modifier(element_modifier, map, block, result) + end + result + end - var unitlessCssProperties = {}; + # nesting - for (var i = 0; i < UNITLESS_NUMBER_PROPS.length; i++) { - var prop = UNITLESS_NUMBER_PROPS[i]; + def selector(parent_selectors, css_selector) + parent_selectors = '' if parent_selectors.include?(':global') + parents = parent_selectors.split(',') + selectors = css_selector.split(',') + result = [] - unitlessCssProperties[prop] = 1; - unitlessCssProperties['-webkit-' + prop] = 1; - unitlessCssProperties['-ms-' + prop] = 1; - unitlessCssProperties['-moz-' + prop] = 1; - unitlessCssProperties['-o-' + prop] = 1; - } + selectors.each do |sel| + pos = sel.index('&') - self.unitless = function (renderer) { - var decl = renderer.decl; + if pos + if parents.empty? + replaced_selector = sel.gsub(/&/, parent) + result.push(replaced_selector) + else + parents.each do |parent| + replaced_selector = sel.gsub(/&/, parent) + result.push(replaced_selector) + end + end + else + if parents.empty? + result.push(sel) + else + parents.each do |parent| + result.push(parent + ' ' + sel) + end + end + end + end - renderer.decl = function (prop, value) { - var str = decl(prop, value); + return result.join(',') + end - if (typeof value === 'number') { - var pos = str.indexOf(':'); - var propKebab = str.substr(0, pos); + # hydrate - if (!unitlessCssProperties[propKebab]) { - return decl(prop, value + 'px'); - } - } + def hydrate(sh) + css_rules = sh.JS[:cssRules] || sh.JS[:sheet].JS[:cssRules] - return str; - }; - }; - } - - %x{ - self.global = function (renderer) { - var selector = renderer.selector; - - renderer.selector = function (parent, current) { - if (parent.indexOf(':global') > -1) parent = ''; - - return selector(parent, current); - }; - - renderer.global = function (css) { - return renderer.put('', css); - }; - }; - } - - %x{ - self.keyframes = function (renderer, config) { - config = renderer.assign({ - prefixes: ['-webkit-', '-moz-', '-o-', ''], - }, config || {}); - - var prefixes = config.prefixes; - - if (renderer.client) { - // Craete @keyframe Stylesheet `ksh`. - document.head.appendChild(renderer.ksh = document.createElement('style')); + %x{ + let i = 0; + let st; + for(i=0; i<css_rules.length; i++) { + st = css_rules[i].selectorText; + if (st) { #{@hydrated[`st`] = 1}; } } + } + end - var putAt = renderer.putAt; - renderer.putAt = function (__, keyframes, prelude) { - // @keyframes - if (prelude[1] === 'k') { - var str = ''; + # global - for (var keyframe in keyframes) { - var decls = keyframes[keyframe]; - var strDecls = ''; + def global(css) + put('', css) + end - for (var prop in decls) - strDecls += renderer.decl(prop, decls[prop]); + # keyframes - str += keyframe + '{' + strDecls + '}'; - } - - for (var i = 0; i < prefixes.length; i++) { - var prefix = prefixes[i]; - var rawKeyframes = prelude.replace('@keyframes', '@' + prefix + 'keyframes') + '{' + str + '}'; - - if (renderer.client) { - renderer.ksh.appendChild(document.createTextNode(rawKeyframes)); - } else { - renderer.putRaw(rawKeyframes); - } - } - - return; - } - - putAt(__, keyframes, prelude); - }; - - renderer.keyframes = function (keyframes, block) { - if (!block) block = renderer.hash(keyframes); - block = renderer.pfx + block; - - renderer.putAt('', keyframes, '@keyframes ' + block); - - return block; - }; - }; - } - - %x{ - self.fadeIn = function (renderer) { - renderer.put('', { - '@keyframes fadeIn': { - from: { - opacity: 0, - }, - to: { - opacity: 1, - } - }, - - '.fadeIn': { - animation: 'fadeIn .4s linear', - } - }); - }; - } - - %x{ - self.fadeOut = function (renderer) { - renderer.put('', { - '@keyframes fadeOut': { - from: { - opacity: 1, - }, - to: { - opacity: 0, - } - }, - - '.fadeOut': { - animation: 'fadeOut .3s linear', - 'animation-fill-mode': 'forwards', - } - }); - }; - } -end \ No newline at end of file + def keyframes(keyframes, cblock = nil) + block = hash(keyframes) unless block + block = @renderer[:pfx] + block + put_at('', keyframes, '@keyframes' + block) + block + end +end