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