storybook-addon-pseudo-states 1.15.1 → 1.15.2--canary.45.07abc1b.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,21 +4,13 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.PseudoStateTool = void 0;
7
-
8
7
  var _react = _interopRequireWildcard(require("react"));
9
-
10
8
  var _api = require("@storybook/api");
11
-
12
9
  var _components = require("@storybook/components");
13
-
14
10
  var _theming = require("@storybook/theming");
15
-
16
11
  var _constants = require("./constants");
17
-
18
12
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
19
-
20
13
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
21
-
22
14
  const LinkTitle = _theming.styled.span(_ref => {
23
15
  let {
24
16
  active
@@ -27,7 +19,6 @@ const LinkTitle = _theming.styled.span(_ref => {
27
19
  color: active ? _theming.color.secondary : "inherit"
28
20
  };
29
21
  });
30
-
31
22
  const LinkIcon = (0, _theming.styled)(_components.Icons)(_ref2 => {
32
23
  let {
33
24
  active
@@ -40,14 +31,14 @@ const LinkIcon = (0, _theming.styled)(_components.Icons)(_ref2 => {
40
31
  };
41
32
  });
42
33
  const options = Object.keys(_constants.PSEUDO_STATES).sort();
43
-
44
34
  const PseudoStateTool = () => {
45
35
  const [{
46
36
  pseudo
47
37
  }, updateGlobals] = (0, _api.useGlobals)();
48
- const isActive = (0, _react.useCallback)(option => (pseudo === null || pseudo === void 0 ? void 0 : pseudo[option]) === true, [pseudo]);
38
+ const isActive = (0, _react.useCallback)(option => pseudo?.[option] === true, [pseudo]);
49
39
  const toggleOption = (0, _react.useCallback)(option => () => updateGlobals({
50
- pseudo: { ...pseudo,
40
+ pseudo: {
41
+ ...pseudo,
51
42
  [option]: !isActive(option)
52
43
  }
53
44
  }), [pseudo]);
@@ -78,5 +69,4 @@ const PseudoStateTool = () => {
78
69
  icon: "button"
79
70
  })));
80
71
  };
81
-
82
72
  exports.PseudoStateTool = PseudoStateTool;
@@ -3,13 +3,19 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.TOOL_ID = exports.PSEUDO_STATES = exports.ADDON_ID = void 0;
6
+ exports.TOOL_ID = exports.PSEUDO_STATES = exports.EXCLUDED_PSEUDO_ELEMENTS = exports.ADDON_ID = void 0;
7
7
  const ADDON_ID = "storybook/pseudo-states";
8
8
  exports.ADDON_ID = ADDON_ID;
9
- const TOOL_ID = "".concat(ADDON_ID, "/tool"); // Dynamic pseudo-classes
10
- // @see https://www.w3.org/TR/2018/REC-selectors-3-20181106/#dynamic-pseudos
9
+ const TOOL_ID = `${ADDON_ID}/tool`;
11
10
 
11
+ // Pseudo-classes, which are not allowed to have classes applied on
12
+ // e.g. ::-webkit-scrollbar-thumb.pseudo-hover is not a valid selector
12
13
  exports.TOOL_ID = TOOL_ID;
14
+ const EXCLUDED_PSEUDO_ELEMENTS = ["::-webkit-scrollbar-thumb"];
15
+
16
+ // Dynamic pseudo-classes
17
+ // @see https://www.w3.org/TR/2018/REC-selectors-3-20181106/#dynamic-pseudos
18
+ exports.EXCLUDED_PSEUDO_ELEMENTS = EXCLUDED_PSEUDO_ELEMENTS;
13
19
  const PSEUDO_STATES = {
14
20
  hover: "hover",
15
21
  active: "active",
@@ -1,11 +1,8 @@
1
1
  "use strict";
2
2
 
3
3
  var _addons = require("@storybook/addons");
4
-
5
4
  var _constants = require("../constants");
6
-
7
5
  var _PseudoStateTool = require("../PseudoStateTool");
8
-
9
6
  _addons.addons.register(_constants.ADDON_ID, () => {
10
7
  _addons.addons.add(_constants.TOOL_ID, {
11
8
  type: _addons.types.TOOL,
@@ -4,8 +4,6 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.decorators = void 0;
7
-
8
7
  var _withPseudoState = require("../withPseudoState");
9
-
10
8
  const decorators = [_withPseudoState.withPseudoState];
11
9
  exports.decorators = decorators;
@@ -4,58 +4,55 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.rewriteStyleSheet = void 0;
7
-
8
7
  var _constants = require("./constants");
9
-
10
8
  var _splitSelectors = require("./splitSelectors");
11
-
12
9
  const pseudoStates = Object.values(_constants.PSEUDO_STATES);
13
- const matchOne = new RegExp(":(".concat(pseudoStates.join("|"), ")"));
14
- const matchAll = new RegExp(":(".concat(pseudoStates.join("|"), ")"), "g");
10
+ const matchOne = new RegExp(`:(${pseudoStates.join("|")})`);
11
+ const matchAll = new RegExp(`:(${pseudoStates.join("|")})`, "g");
15
12
  const warnings = new Set();
16
-
17
13
  const warnOnce = message => {
18
- if (warnings.has(message)) return; // eslint-disable-next-line no-console
19
-
14
+ if (warnings.has(message)) return;
15
+ // eslint-disable-next-line no-console
20
16
  console.warn(message);
21
17
  warnings.add(message);
22
18
  };
23
-
24
19
  const rewriteRule = (cssText, selectorText, shadowRoot) => {
25
20
  return cssText.replace(selectorText, (0, _splitSelectors.splitSelectors)(selectorText).flatMap(selector => {
26
21
  if (selector.includes(".pseudo-")) {
27
22
  return [];
28
23
  }
29
-
30
24
  if (!matchOne.test(selector)) {
31
25
  return [selector];
32
26
  }
33
-
34
27
  const states = [];
35
28
  const plainSelector = selector.replace(matchAll, (_, state) => {
36
29
  states.push(state);
37
30
  return "";
38
31
  });
39
- const classSelector = states.reduce((acc, state) => acc.replace(new RegExp(":".concat(state), "g"), ".pseudo-".concat(state)), selector);
40
-
32
+ const getDoesSelectorEndsWithExcludedPseudoElement = pseudo => _constants.EXCLUDED_PSEUDO_ELEMENTS.some(element => {
33
+ return selector.endsWith(`${element}:${pseudo}`);
34
+ });
35
+ const classSelector = states.reduce((acc, state) => {
36
+ if (getDoesSelectorEndsWithExcludedPseudoElement(state)) {
37
+ return undefined;
38
+ }
39
+ return acc.replace(new RegExp(`(?<!Y):${state}`, "g"), `.pseudo-${state}`);
40
+ }, selector);
41
41
  if (selector.startsWith(":host(") || selector.startsWith("::slotted(")) {
42
42
  return [selector, classSelector];
43
43
  }
44
-
45
- const ancestorSelector = shadowRoot ? ":host(".concat(states.map(s => ".pseudo-".concat(s)).join(""), ") ").concat(plainSelector) : "".concat(states.map(s => ".pseudo-".concat(s)).join(""), " ").concat(plainSelector);
46
- return [selector, classSelector, ancestorSelector].filter(selector => !selector.includes(":not()"));
44
+ const ancestorSelector = shadowRoot ? `:host(${states.map(s => `.pseudo-${s}`).join("")}) ${plainSelector}` : `${states.map(s => `.pseudo-${s}`).join("")} ${plainSelector}`;
45
+ return [selector, classSelector, ancestorSelector].filter(selector => !selector?.includes(":not()")).filter(Boolean);
47
46
  }).join(", "));
48
- }; // Rewrites the style sheet to add alternative selectors for any rule that targets a pseudo state.
49
- // A sheet can only be rewritten once, and may carry over between stories.
50
-
47
+ };
51
48
 
49
+ // Rewrites the style sheet to add alternative selectors for any rule that targets a pseudo state.
50
+ // A sheet can only be rewritten once, and may carry over between stories.
52
51
  const rewriteStyleSheet = (sheet, shadowRoot, shadowHosts) => {
53
52
  if (sheet.__pseudoStatesRewritten) return;
54
53
  sheet.__pseudoStatesRewritten = true;
55
-
56
54
  try {
57
55
  let index = 0;
58
-
59
56
  for (const {
60
57
  cssText,
61
58
  selectorText
@@ -66,9 +63,7 @@ const rewriteStyleSheet = (sheet, shadowRoot, shadowHosts) => {
66
63
  sheet.insertRule(newRule, index);
67
64
  if (shadowRoot) shadowHosts.add(shadowRoot.host);
68
65
  }
69
-
70
66
  index++;
71
-
72
67
  if (index > 1000) {
73
68
  warnOnce("Reached maximum of 1000 pseudo selectors per sheet, skipping the rest.");
74
69
  break;
@@ -76,12 +71,11 @@ const rewriteStyleSheet = (sheet, shadowRoot, shadowHosts) => {
76
71
  }
77
72
  } catch (e) {
78
73
  if (e.toString().includes("cssRules")) {
79
- warnOnce("Can't access cssRules, likely due to CORS restrictions: ".concat(sheet.href));
74
+ warnOnce(`Can't access cssRules, likely due to CORS restrictions: ${sheet.href}`);
80
75
  } else {
81
76
  // eslint-disable-next-line no-console
82
77
  console.error(e, sheet.href);
83
78
  }
84
79
  }
85
80
  };
86
-
87
81
  exports.rewriteStyleSheet = rewriteStyleSheet;
@@ -1,31 +1,24 @@
1
1
  "use strict";
2
2
 
3
3
  var _rewriteStyleSheet = require("./rewriteStyleSheet");
4
-
5
4
  class Sheet {
6
5
  constructor() {
7
6
  this.__pseudoStatesRewritten = false;
8
-
9
7
  for (var _len = arguments.length, rules = new Array(_len), _key = 0; _key < _len; _key++) {
10
8
  rules[_key] = arguments[_key];
11
9
  }
12
-
13
10
  this.cssRules = rules.map(cssText => ({
14
11
  cssText,
15
12
  selectorText: cssText.slice(0, cssText.indexOf(" {"))
16
13
  }));
17
14
  }
18
-
19
15
  deleteRule(index) {
20
16
  this.cssRules.splice(index, 1);
21
17
  }
22
-
23
18
  insertRule(cssText, index) {
24
19
  this.cssRules.splice(index, 0, cssText);
25
20
  }
26
-
27
21
  }
28
-
29
22
  describe("rewriteStyleSheet", () => {
30
23
  it("adds alternative selector targeting the element directly", () => {
31
24
  const sheet = new Sheet("a:hover { color: red }");
@@ -37,6 +30,12 @@ describe("rewriteStyleSheet", () => {
37
30
  (0, _rewriteStyleSheet.rewriteStyleSheet)(sheet);
38
31
  expect(sheet.cssRules[0]).toContain(".pseudo-hover a");
39
32
  });
33
+ it("does not add .pseudo-<class> to pseudo-class, which does not support classes", () => {
34
+ const sheet = new Sheet("::-webkit-scrollbar-thumb:hover { border-color: transparent; }");
35
+ (0, _rewriteStyleSheet.rewriteStyleSheet)(sheet);
36
+ console.log(sheet.cssRules[0]);
37
+ expect(sheet.cssRules[0]).not.toContain("::-webkit-scrollbar-thumb.pseudo-hover");
38
+ });
40
39
  it("adds alternative selector for each pseudo selector", () => {
41
40
  const sheet = new Sheet("a:hover, a:focus { color: red }");
42
41
  (0, _rewriteStyleSheet.rewriteStyleSheet)(sheet);
@@ -4,19 +4,15 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.splitSelectors = void 0;
7
-
8
7
  const isAtRule = selector => selector.indexOf("@") === 0;
9
-
10
8
  const splitSelectors = selectors => {
11
9
  if (isAtRule(selectors)) return [selectors];
12
10
  let result = [];
13
11
  let parentheses = 0;
14
12
  let brackets = 0;
15
13
  let selector = "";
16
-
17
14
  for (let i = 0, len = selectors.length; i < len; i++) {
18
15
  const char = selectors[i];
19
-
20
16
  if (char === "(") {
21
17
  parentheses += 1;
22
18
  } else if (char === ")") {
@@ -32,12 +28,9 @@ const splitSelectors = selectors => {
32
28
  continue;
33
29
  }
34
30
  }
35
-
36
31
  selector += char;
37
32
  }
38
-
39
33
  result.push(selector.trim());
40
34
  return result;
41
35
  };
42
-
43
36
  exports.splitSelectors = splitSelectors;
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
 
3
3
  var _splitSelectors = require("./splitSelectors");
4
-
5
4
  describe("splitSelectors", () => {
6
5
  test("handles basic selectors", () => {
7
6
  expect((0, _splitSelectors.splitSelectors)(".a")).toEqual([".a"]);
@@ -4,33 +4,25 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.withPseudoState = void 0;
7
-
8
7
  var _addons = require("@storybook/addons");
9
-
10
8
  var _coreEvents = require("@storybook/core-events");
11
-
12
9
  var _constants = require("./constants");
13
-
14
10
  var _rewriteStyleSheet = require("./rewriteStyleSheet");
15
-
16
11
  /* eslint-env browser */
12
+
17
13
  const channel = _addons.addons.getChannel();
14
+ const shadowHosts = new Set();
18
15
 
19
- const shadowHosts = new Set(); // Drops any existing pseudo state classnames that carried over from a previously viewed story
16
+ // Drops any existing pseudo state classnames that carried over from a previously viewed story
20
17
  // before adding the new classnames. We do this the old-fashioned way, for IE compatibility.
21
-
22
18
  const applyClasses = (element, classnames) => {
23
19
  element.className = element.className.split(" ").filter(classname => classname && classname.indexOf("pseudo-") !== 0).concat(...classnames).join(" ");
24
20
  };
25
-
26
21
  const applyParameter = (rootElement, parameter) => {
27
22
  const map = new Map([[rootElement, new Set()]]);
28
-
29
23
  const add = (target, state) => map.set(target, new Set([...(map.get(target) || []), state]));
30
-
31
24
  Object.entries(parameter || {}).forEach(_ref => {
32
25
  let [state, value] = _ref;
33
-
34
26
  if (typeof value === "boolean") {
35
27
  // default API - applying pseudo class to root element.
36
28
  add(rootElement, value && state);
@@ -44,25 +36,23 @@ const applyParameter = (rootElement, parameter) => {
44
36
  });
45
37
  map.forEach((states, target) => {
46
38
  const classnames = [];
47
- states.forEach(key => _constants.PSEUDO_STATES[key] && classnames.push("pseudo-".concat(_constants.PSEUDO_STATES[key])));
39
+ states.forEach(key => _constants.PSEUDO_STATES[key] && classnames.push(`pseudo-${_constants.PSEUDO_STATES[key]}`));
48
40
  applyClasses(target, classnames);
49
41
  });
50
- }; // Traverses ancestry to collect relevant pseudo classnames, and applies them to the shadow host.
51
- // Shadow DOM can only access classes on its host. Traversing is needed to mimic the CSS cascade.
52
-
42
+ };
53
43
 
44
+ // Traverses ancestry to collect relevant pseudo classnames, and applies them to the shadow host.
45
+ // Shadow DOM can only access classes on its host. Traversing is needed to mimic the CSS cascade.
54
46
  const updateShadowHost = shadowHost => {
55
47
  const classnames = new Set();
56
-
57
48
  for (let element = shadowHost.parentElement; element; element = element.parentElement) {
58
49
  if (!element.className) continue;
59
50
  element.className.split(" ").filter(classname => classname.indexOf("pseudo-") === 0).forEach(classname => classnames.add(classname));
60
51
  }
61
-
62
52
  applyClasses(shadowHost, classnames);
63
- }; // Global decorator that rewrites stylesheets and applies classnames to render pseudo styles
64
-
53
+ };
65
54
 
55
+ // Global decorator that rewrites stylesheets and applies classnames to render pseudo styles
66
56
  const withPseudoState = (StoryFn, _ref2) => {
67
57
  let {
68
58
  viewMode,
@@ -75,9 +65,10 @@ const withPseudoState = (StoryFn, _ref2) => {
75
65
  } = parameters;
76
66
  const {
77
67
  pseudo: globals
78
- } = globalsArgs; // Sync parameter to globals, used by the toolbar (only in canvas as this
79
- // doesn't make sense for docs because many stories are displayed at once)
68
+ } = globalsArgs;
80
69
 
70
+ // Sync parameter to globals, used by the toolbar (only in canvas as this
71
+ // doesn't make sense for docs because many stories are displayed at once)
81
72
  (0, _addons.useEffect)(() => {
82
73
  if (parameter !== globals && viewMode === "story") {
83
74
  channel.emit(_coreEvents.UPDATE_GLOBALS, {
@@ -86,49 +77,49 @@ const withPseudoState = (StoryFn, _ref2) => {
86
77
  }
87
78
  });
88
79
  }
89
- }, [parameter, viewMode]); // Convert selected states to classnames and apply them to the story root element.
90
- // Then update each shadow host to redetermine its own pseudo classnames.
80
+ }, [parameter, viewMode]);
91
81
 
82
+ // Convert selected states to classnames and apply them to the story root element.
83
+ // Then update each shadow host to redetermine its own pseudo classnames.
92
84
  (0, _addons.useEffect)(() => {
93
85
  const timeout = setTimeout(() => {
94
- const element = document.getElementById(viewMode === "docs" ? "story--".concat(id) : "root");
86
+ const element = document.getElementById(viewMode === "docs" ? `story--${id}` : `root`);
95
87
  applyParameter(element, globals || parameter);
96
88
  shadowHosts.forEach(updateShadowHost);
97
89
  }, 0);
98
90
  return () => clearTimeout(timeout);
99
91
  }, [globals, parameter, viewMode]);
100
92
  return StoryFn();
101
- }; // Rewrite CSS rules for pseudo-states on all stylesheets to add an alternative selector
102
-
93
+ };
103
94
 
95
+ // Rewrite CSS rules for pseudo-states on all stylesheets to add an alternative selector
104
96
  exports.withPseudoState = withPseudoState;
105
-
106
97
  const rewriteStyleSheets = shadowRoot => {
107
- var _shadowRoot$adoptedSt;
108
-
109
98
  let styleSheets = shadowRoot ? shadowRoot.styleSheets : document.styleSheets;
110
- if (shadowRoot !== null && shadowRoot !== void 0 && (_shadowRoot$adoptedSt = shadowRoot.adoptedStyleSheets) !== null && _shadowRoot$adoptedSt !== void 0 && _shadowRoot$adoptedSt.length) styleSheets = shadowRoot.adoptedStyleSheets;
99
+ if (shadowRoot?.adoptedStyleSheets?.length) styleSheets = shadowRoot.adoptedStyleSheets;
111
100
  Array.from(styleSheets).forEach(sheet => (0, _rewriteStyleSheet.rewriteStyleSheet)(sheet, shadowRoot, shadowHosts));
112
- }; // Only track shadow hosts for the current story
113
-
101
+ };
114
102
 
115
- channel.on(_coreEvents.STORY_CHANGED, () => shadowHosts.clear()); // Reinitialize CSS enhancements every time the story changes
103
+ // Only track shadow hosts for the current story
104
+ channel.on(_coreEvents.STORY_CHANGED, () => shadowHosts.clear());
116
105
 
117
- channel.on(_coreEvents.STORY_RENDERED, () => rewriteStyleSheets()); // Reinitialize CSS enhancements every time a docs page is rendered
106
+ // Reinitialize CSS enhancements every time the story changes
107
+ channel.on(_coreEvents.STORY_RENDERED, () => rewriteStyleSheets());
118
108
 
119
- channel.on(_coreEvents.DOCS_RENDERED, () => rewriteStyleSheets()); // IE doesn't support shadow DOM
109
+ // Reinitialize CSS enhancements every time a docs page is rendered
110
+ channel.on(_coreEvents.DOCS_RENDERED, () => rewriteStyleSheets());
120
111
 
112
+ // IE doesn't support shadow DOM
121
113
  if (Element.prototype.attachShadow) {
122
114
  // Monkeypatch the attachShadow method so we can handle pseudo styles inside shadow DOM
123
115
  Element.prototype._attachShadow = Element.prototype.attachShadow;
124
-
125
116
  Element.prototype.attachShadow = function attachShadow(init) {
126
117
  // Force "open" mode, so we can access the shadowRoot
127
- const shadowRoot = this._attachShadow({ ...init,
118
+ const shadowRoot = this._attachShadow({
119
+ ...init,
128
120
  mode: "open"
129
- }); // Wait for it to render and apply its styles before rewriting them
130
-
131
-
121
+ });
122
+ // Wait for it to render and apply its styles before rewriting them
132
123
  requestAnimationFrame(() => {
133
124
  rewriteStyleSheets(shadowRoot);
134
125
  updateShadowHost(shadowRoot.host);
@@ -27,9 +27,10 @@ export const PseudoStateTool = () => {
27
27
  const [{
28
28
  pseudo
29
29
  }, updateGlobals] = useGlobals();
30
- const isActive = useCallback(option => (pseudo === null || pseudo === void 0 ? void 0 : pseudo[option]) === true, [pseudo]);
30
+ const isActive = useCallback(option => pseudo?.[option] === true, [pseudo]);
31
31
  const toggleOption = useCallback(option => () => updateGlobals({
32
- pseudo: { ...pseudo,
32
+ pseudo: {
33
+ ...pseudo,
33
34
  [option]: !isActive(option)
34
35
  }
35
36
  }), [pseudo]);
@@ -1,7 +1,12 @@
1
1
  export const ADDON_ID = "storybook/pseudo-states";
2
- export const TOOL_ID = "".concat(ADDON_ID, "/tool"); // Dynamic pseudo-classes
3
- // @see https://www.w3.org/TR/2018/REC-selectors-3-20181106/#dynamic-pseudos
2
+ export const TOOL_ID = `${ADDON_ID}/tool`;
3
+
4
+ // Pseudo-classes, which are not allowed to have classes applied on
5
+ // e.g. ::-webkit-scrollbar-thumb.pseudo-hover is not a valid selector
6
+ export const EXCLUDED_PSEUDO_ELEMENTS = ["::-webkit-scrollbar-thumb"];
4
7
 
8
+ // Dynamic pseudo-classes
9
+ // @see https://www.w3.org/TR/2018/REC-selectors-3-20181106/#dynamic-pseudos
5
10
  export const PSEUDO_STATES = {
6
11
  hover: "hover",
7
12
  active: "active",
@@ -1,52 +1,52 @@
1
- import { PSEUDO_STATES } from "./constants";
1
+ import { PSEUDO_STATES, EXCLUDED_PSEUDO_ELEMENTS } from "./constants";
2
2
  import { splitSelectors } from "./splitSelectors";
3
3
  const pseudoStates = Object.values(PSEUDO_STATES);
4
- const matchOne = new RegExp(":(".concat(pseudoStates.join("|"), ")"));
5
- const matchAll = new RegExp(":(".concat(pseudoStates.join("|"), ")"), "g");
4
+ const matchOne = new RegExp(`:(${pseudoStates.join("|")})`);
5
+ const matchAll = new RegExp(`:(${pseudoStates.join("|")})`, "g");
6
6
  const warnings = new Set();
7
-
8
7
  const warnOnce = message => {
9
- if (warnings.has(message)) return; // eslint-disable-next-line no-console
10
-
8
+ if (warnings.has(message)) return;
9
+ // eslint-disable-next-line no-console
11
10
  console.warn(message);
12
11
  warnings.add(message);
13
12
  };
14
-
15
13
  const rewriteRule = (cssText, selectorText, shadowRoot) => {
16
14
  return cssText.replace(selectorText, splitSelectors(selectorText).flatMap(selector => {
17
15
  if (selector.includes(".pseudo-")) {
18
16
  return [];
19
17
  }
20
-
21
18
  if (!matchOne.test(selector)) {
22
19
  return [selector];
23
20
  }
24
-
25
21
  const states = [];
26
22
  const plainSelector = selector.replace(matchAll, (_, state) => {
27
23
  states.push(state);
28
24
  return "";
29
25
  });
30
- const classSelector = states.reduce((acc, state) => acc.replace(new RegExp(":".concat(state), "g"), ".pseudo-".concat(state)), selector);
31
-
26
+ const getDoesSelectorEndsWithExcludedPseudoElement = pseudo => EXCLUDED_PSEUDO_ELEMENTS.some(element => {
27
+ return selector.endsWith(`${element}:${pseudo}`);
28
+ });
29
+ const classSelector = states.reduce((acc, state) => {
30
+ if (getDoesSelectorEndsWithExcludedPseudoElement(state)) {
31
+ return undefined;
32
+ }
33
+ return acc.replace(new RegExp(`(?<!Y):${state}`, "g"), `.pseudo-${state}`);
34
+ }, selector);
32
35
  if (selector.startsWith(":host(") || selector.startsWith("::slotted(")) {
33
36
  return [selector, classSelector];
34
37
  }
35
-
36
- const ancestorSelector = shadowRoot ? ":host(".concat(states.map(s => ".pseudo-".concat(s)).join(""), ") ").concat(plainSelector) : "".concat(states.map(s => ".pseudo-".concat(s)).join(""), " ").concat(plainSelector);
37
- return [selector, classSelector, ancestorSelector].filter(selector => !selector.includes(":not()"));
38
+ const ancestorSelector = shadowRoot ? `:host(${states.map(s => `.pseudo-${s}`).join("")}) ${plainSelector}` : `${states.map(s => `.pseudo-${s}`).join("")} ${plainSelector}`;
39
+ return [selector, classSelector, ancestorSelector].filter(selector => !selector?.includes(":not()")).filter(Boolean);
38
40
  }).join(", "));
39
- }; // Rewrites the style sheet to add alternative selectors for any rule that targets a pseudo state.
40
- // A sheet can only be rewritten once, and may carry over between stories.
41
-
41
+ };
42
42
 
43
+ // Rewrites the style sheet to add alternative selectors for any rule that targets a pseudo state.
44
+ // A sheet can only be rewritten once, and may carry over between stories.
43
45
  export const rewriteStyleSheet = (sheet, shadowRoot, shadowHosts) => {
44
46
  if (sheet.__pseudoStatesRewritten) return;
45
47
  sheet.__pseudoStatesRewritten = true;
46
-
47
48
  try {
48
49
  let index = 0;
49
-
50
50
  for (const {
51
51
  cssText,
52
52
  selectorText
@@ -57,9 +57,7 @@ export const rewriteStyleSheet = (sheet, shadowRoot, shadowHosts) => {
57
57
  sheet.insertRule(newRule, index);
58
58
  if (shadowRoot) shadowHosts.add(shadowRoot.host);
59
59
  }
60
-
61
60
  index++;
62
-
63
61
  if (index > 1000) {
64
62
  warnOnce("Reached maximum of 1000 pseudo selectors per sheet, skipping the rest.");
65
63
  break;
@@ -67,7 +65,7 @@ export const rewriteStyleSheet = (sheet, shadowRoot, shadowHosts) => {
67
65
  }
68
66
  } catch (e) {
69
67
  if (e.toString().includes("cssRules")) {
70
- warnOnce("Can't access cssRules, likely due to CORS restrictions: ".concat(sheet.href));
68
+ warnOnce(`Can't access cssRules, likely due to CORS restrictions: ${sheet.href}`);
71
69
  } else {
72
70
  // eslint-disable-next-line no-console
73
71
  console.error(e, sheet.href);
@@ -1,29 +1,22 @@
1
1
  import { rewriteStyleSheet } from "./rewriteStyleSheet";
2
-
3
2
  class Sheet {
4
3
  constructor() {
5
4
  this.__pseudoStatesRewritten = false;
6
-
7
5
  for (var _len = arguments.length, rules = new Array(_len), _key = 0; _key < _len; _key++) {
8
6
  rules[_key] = arguments[_key];
9
7
  }
10
-
11
8
  this.cssRules = rules.map(cssText => ({
12
9
  cssText,
13
10
  selectorText: cssText.slice(0, cssText.indexOf(" {"))
14
11
  }));
15
12
  }
16
-
17
13
  deleteRule(index) {
18
14
  this.cssRules.splice(index, 1);
19
15
  }
20
-
21
16
  insertRule(cssText, index) {
22
17
  this.cssRules.splice(index, 0, cssText);
23
18
  }
24
-
25
19
  }
26
-
27
20
  describe("rewriteStyleSheet", () => {
28
21
  it("adds alternative selector targeting the element directly", () => {
29
22
  const sheet = new Sheet("a:hover { color: red }");
@@ -35,6 +28,12 @@ describe("rewriteStyleSheet", () => {
35
28
  rewriteStyleSheet(sheet);
36
29
  expect(sheet.cssRules[0]).toContain(".pseudo-hover a");
37
30
  });
31
+ it("does not add .pseudo-<class> to pseudo-class, which does not support classes", () => {
32
+ const sheet = new Sheet("::-webkit-scrollbar-thumb:hover { border-color: transparent; }");
33
+ rewriteStyleSheet(sheet);
34
+ console.log(sheet.cssRules[0]);
35
+ expect(sheet.cssRules[0]).not.toContain("::-webkit-scrollbar-thumb.pseudo-hover");
36
+ });
38
37
  it("adds alternative selector for each pseudo selector", () => {
39
38
  const sheet = new Sheet("a:hover, a:focus { color: red }");
40
39
  rewriteStyleSheet(sheet);
@@ -1,15 +1,12 @@
1
1
  const isAtRule = selector => selector.indexOf("@") === 0;
2
-
3
2
  export const splitSelectors = selectors => {
4
3
  if (isAtRule(selectors)) return [selectors];
5
4
  let result = [];
6
5
  let parentheses = 0;
7
6
  let brackets = 0;
8
7
  let selector = "";
9
-
10
8
  for (let i = 0, len = selectors.length; i < len; i++) {
11
9
  const char = selectors[i];
12
-
13
10
  if (char === "(") {
14
11
  parentheses += 1;
15
12
  } else if (char === ")") {
@@ -25,10 +22,8 @@ export const splitSelectors = selectors => {
25
22
  continue;
26
23
  }
27
24
  }
28
-
29
25
  selector += char;
30
26
  }
31
-
32
27
  result.push(selector.trim());
33
28
  return result;
34
29
  };
@@ -4,21 +4,18 @@ import { DOCS_RENDERED, STORY_CHANGED, STORY_RENDERED, UPDATE_GLOBALS } from "@s
4
4
  import { PSEUDO_STATES } from "./constants";
5
5
  import { rewriteStyleSheet } from "./rewriteStyleSheet";
6
6
  const channel = addons.getChannel();
7
- const shadowHosts = new Set(); // Drops any existing pseudo state classnames that carried over from a previously viewed story
8
- // before adding the new classnames. We do this the old-fashioned way, for IE compatibility.
7
+ const shadowHosts = new Set();
9
8
 
9
+ // Drops any existing pseudo state classnames that carried over from a previously viewed story
10
+ // before adding the new classnames. We do this the old-fashioned way, for IE compatibility.
10
11
  const applyClasses = (element, classnames) => {
11
12
  element.className = element.className.split(" ").filter(classname => classname && classname.indexOf("pseudo-") !== 0).concat(...classnames).join(" ");
12
13
  };
13
-
14
14
  const applyParameter = (rootElement, parameter) => {
15
15
  const map = new Map([[rootElement, new Set()]]);
16
-
17
16
  const add = (target, state) => map.set(target, new Set([...(map.get(target) || []), state]));
18
-
19
17
  Object.entries(parameter || {}).forEach(_ref => {
20
18
  let [state, value] = _ref;
21
-
22
19
  if (typeof value === "boolean") {
23
20
  // default API - applying pseudo class to root element.
24
21
  add(rootElement, value && state);
@@ -32,25 +29,23 @@ const applyParameter = (rootElement, parameter) => {
32
29
  });
33
30
  map.forEach((states, target) => {
34
31
  const classnames = [];
35
- states.forEach(key => PSEUDO_STATES[key] && classnames.push("pseudo-".concat(PSEUDO_STATES[key])));
32
+ states.forEach(key => PSEUDO_STATES[key] && classnames.push(`pseudo-${PSEUDO_STATES[key]}`));
36
33
  applyClasses(target, classnames);
37
34
  });
38
- }; // Traverses ancestry to collect relevant pseudo classnames, and applies them to the shadow host.
39
- // Shadow DOM can only access classes on its host. Traversing is needed to mimic the CSS cascade.
40
-
35
+ };
41
36
 
37
+ // Traverses ancestry to collect relevant pseudo classnames, and applies them to the shadow host.
38
+ // Shadow DOM can only access classes on its host. Traversing is needed to mimic the CSS cascade.
42
39
  const updateShadowHost = shadowHost => {
43
40
  const classnames = new Set();
44
-
45
41
  for (let element = shadowHost.parentElement; element; element = element.parentElement) {
46
42
  if (!element.className) continue;
47
43
  element.className.split(" ").filter(classname => classname.indexOf("pseudo-") === 0).forEach(classname => classnames.add(classname));
48
44
  }
49
-
50
45
  applyClasses(shadowHost, classnames);
51
- }; // Global decorator that rewrites stylesheets and applies classnames to render pseudo styles
52
-
46
+ };
53
47
 
48
+ // Global decorator that rewrites stylesheets and applies classnames to render pseudo styles
54
49
  export const withPseudoState = (StoryFn, _ref2) => {
55
50
  let {
56
51
  viewMode,
@@ -63,9 +58,10 @@ export const withPseudoState = (StoryFn, _ref2) => {
63
58
  } = parameters;
64
59
  const {
65
60
  pseudo: globals
66
- } = globalsArgs; // Sync parameter to globals, used by the toolbar (only in canvas as this
67
- // doesn't make sense for docs because many stories are displayed at once)
61
+ } = globalsArgs;
68
62
 
63
+ // Sync parameter to globals, used by the toolbar (only in canvas as this
64
+ // doesn't make sense for docs because many stories are displayed at once)
69
65
  useEffect(() => {
70
66
  if (parameter !== globals && viewMode === "story") {
71
67
  channel.emit(UPDATE_GLOBALS, {
@@ -74,46 +70,48 @@ export const withPseudoState = (StoryFn, _ref2) => {
74
70
  }
75
71
  });
76
72
  }
77
- }, [parameter, viewMode]); // Convert selected states to classnames and apply them to the story root element.
78
- // Then update each shadow host to redetermine its own pseudo classnames.
73
+ }, [parameter, viewMode]);
79
74
 
75
+ // Convert selected states to classnames and apply them to the story root element.
76
+ // Then update each shadow host to redetermine its own pseudo classnames.
80
77
  useEffect(() => {
81
78
  const timeout = setTimeout(() => {
82
- const element = document.getElementById(viewMode === "docs" ? "story--".concat(id) : "root");
79
+ const element = document.getElementById(viewMode === "docs" ? `story--${id}` : `root`);
83
80
  applyParameter(element, globals || parameter);
84
81
  shadowHosts.forEach(updateShadowHost);
85
82
  }, 0);
86
83
  return () => clearTimeout(timeout);
87
84
  }, [globals, parameter, viewMode]);
88
85
  return StoryFn();
89
- }; // Rewrite CSS rules for pseudo-states on all stylesheets to add an alternative selector
86
+ };
90
87
 
88
+ // Rewrite CSS rules for pseudo-states on all stylesheets to add an alternative selector
91
89
  const rewriteStyleSheets = shadowRoot => {
92
- var _shadowRoot$adoptedSt;
93
-
94
90
  let styleSheets = shadowRoot ? shadowRoot.styleSheets : document.styleSheets;
95
- if (shadowRoot !== null && shadowRoot !== void 0 && (_shadowRoot$adoptedSt = shadowRoot.adoptedStyleSheets) !== null && _shadowRoot$adoptedSt !== void 0 && _shadowRoot$adoptedSt.length) styleSheets = shadowRoot.adoptedStyleSheets;
91
+ if (shadowRoot?.adoptedStyleSheets?.length) styleSheets = shadowRoot.adoptedStyleSheets;
96
92
  Array.from(styleSheets).forEach(sheet => rewriteStyleSheet(sheet, shadowRoot, shadowHosts));
97
- }; // Only track shadow hosts for the current story
98
-
93
+ };
99
94
 
100
- channel.on(STORY_CHANGED, () => shadowHosts.clear()); // Reinitialize CSS enhancements every time the story changes
95
+ // Only track shadow hosts for the current story
96
+ channel.on(STORY_CHANGED, () => shadowHosts.clear());
101
97
 
102
- channel.on(STORY_RENDERED, () => rewriteStyleSheets()); // Reinitialize CSS enhancements every time a docs page is rendered
98
+ // Reinitialize CSS enhancements every time the story changes
99
+ channel.on(STORY_RENDERED, () => rewriteStyleSheets());
103
100
 
104
- channel.on(DOCS_RENDERED, () => rewriteStyleSheets()); // IE doesn't support shadow DOM
101
+ // Reinitialize CSS enhancements every time a docs page is rendered
102
+ channel.on(DOCS_RENDERED, () => rewriteStyleSheets());
105
103
 
104
+ // IE doesn't support shadow DOM
106
105
  if (Element.prototype.attachShadow) {
107
106
  // Monkeypatch the attachShadow method so we can handle pseudo styles inside shadow DOM
108
107
  Element.prototype._attachShadow = Element.prototype.attachShadow;
109
-
110
108
  Element.prototype.attachShadow = function attachShadow(init) {
111
109
  // Force "open" mode, so we can access the shadowRoot
112
- const shadowRoot = this._attachShadow({ ...init,
110
+ const shadowRoot = this._attachShadow({
111
+ ...init,
113
112
  mode: "open"
114
- }); // Wait for it to render and apply its styles before rewriting them
115
-
116
-
113
+ });
114
+ // Wait for it to render and apply its styles before rewriting them
117
115
  requestAnimationFrame(() => {
118
116
  rewriteStyleSheets(shadowRoot);
119
117
  updateShadowHost(shadowRoot.host);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "storybook-addon-pseudo-states",
3
- "version": "1.15.1",
3
+ "version": "1.15.2--canary.45.07abc1b.0",
4
4
  "description": "CSS pseudo states for Storybook",
5
5
  "keywords": [
6
6
  "storybook-addons",