styletron-client/src/styletron-client.js

  1. const DECL_REGEX = /.([^:{]+)(:[^{]+)?{([^:]+):([^}]+)}/g;
  2. const StyletronCore = require('styletron-core');
  3. /**
  4. * A Styletron class for rendering styles in the browser
  5. * @extends StyletronCore
  6. * @packagename styletron-client
  7. * @example
  8. * const elements = document.getElementsByClassName('_styletron_hydrate_');
  9. * const styletron = new StyletronClient(elements);
  10. */
  11. class StyletronClient extends StyletronCore {
  12. /**
  13. * Create a new StyletronClient instance
  14. * @param {NodeList|HTMLCollection|HTMLStyleElement[]} [serverStyles] - List of server style elements
  15. * @param {object} [opts] - StyletronCore options
  16. */
  17. constructor(serverStyles, opts) {
  18. super(opts);
  19. this.uniqueCount = 0;
  20. this.mediaSheets = {};
  21. if (serverStyles) {
  22. for (let i = 0; i < serverStyles.length; i++) {
  23. const element = serverStyles[i];
  24. if (element.media) {
  25. this.mediaSheets[element.media] = element;
  26. } else {
  27. this.mainSheet = element;
  28. }
  29. this.hydrateCacheFromCssString(element.textContent, element.media);
  30. }
  31. } else {
  32. const styleSheet = document.createElement('style');
  33. document.head.appendChild(styleSheet);
  34. this.mainSheet = styleSheet;
  35. }
  36. }
  37. /*
  38. * Hydrate the cache from a css string and media string
  39. * @param {string} css - The stylesheet css content
  40. * @param {string} media - The stylesheet media string
  41. */
  42. hydrateCacheFromCssString(css, media) {
  43. let decl;
  44. // {
  45. // 1: className,
  46. // 2: pseudo,
  47. // 3: prop,
  48. // 4: val
  49. // }
  50. while (decl = DECL_REGEX.exec(css)) {
  51. super.incrementVirtualCount();
  52. StyletronCore.assignDecl(this.cache, {
  53. pseudo: decl[2],
  54. prop: decl[3],
  55. val: decl[4],
  56. media
  57. }, decl[1]);
  58. }
  59. }
  60. /**
  61. * Inject declaration into the stylesheet and return the unique class name
  62. * @return {string} class name
  63. * @example
  64. * // <style id="styletron">.a{color:red}</style>
  65. * const styletron = new StyletronClient(document.getElementsByClassName('_styletron_hydrate_'));
  66. * styletron.injectDeclaration({prop: 'color', val: 'blue'});
  67. * // → 'b'
  68. * styletron.injectDeclaration({prop: 'color', val: 'red', media: '(min-width: 800px)'});
  69. * // → 'c'
  70. * styletron.injectDeclaration({prop: 'color', val: 'red'});
  71. * // → 'a'
  72. */
  73. injectDeclaration(decl) {
  74. const oldCount = this.uniqueCount;
  75. const className = super.injectDeclaration(decl);
  76. if (oldCount !== this.uniqueCount) {
  77. const rule = declarationToRule(className, decl);
  78. let sheet;
  79. if (decl.media) {
  80. if (!this.mediaSheets[decl.media]) {
  81. const mediaSheet = document.createElement('style');
  82. mediaSheet.media = decl.media;
  83. this.mediaSheets[decl.media] = mediaSheet;
  84. this.mainSheet.parentNode.appendChild(mediaSheet);
  85. }
  86. sheet = this.mediaSheets[decl.media].sheet;
  87. } else {
  88. sheet = this.mainSheet.sheet;
  89. }
  90. sheet.insertRule(rule, sheet.cssRules.length);
  91. }
  92. return className;
  93. }
  94. }
  95. module.exports = StyletronClient;
  96. /*
  97. * Injection helpers
  98. */
  99. function declarationToRule(className, {prop, val, pseudo}) {
  100. const decl = `${prop}:${val}`;
  101. let selector = `.${className}`;
  102. if (pseudo) {
  103. selector += pseudo;
  104. }
  105. return `${selector}{${decl}}`;
  106. }