storybook-addon-pseudo-states 2.0.1 → 2.1.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.
package/README.md CHANGED
@@ -13,7 +13,7 @@ Toggle CSS pseudo states for your components in Storybook.
13
13
 
14
14
  ## Introduction
15
15
 
16
- This addon attempts to "force" your components' pseudo states. It rewrites all document stylesheets to add a class name selector to any rules that target a pseudo-class (`:hover`, `:focus`, etc.). The tool then allows you to toggle these class names on the story container (`#root`). Additionally, you can set the `pseudo` property on your story `parameters` to set a default value for each pseudo class. This makes it possible to test such states with [Chromatic](https://www.chromatic.com/).
16
+ This addon attempts to "force" your components' pseudo states. It rewrites all document stylesheets to add a class name selector to any rules that target a pseudo-class (`:hover`, `:focus`, etc.). The tool then allows you to toggle these class names on the story container (`#root`) or any other root element you want (via the `rootSelector` param). Additionally, you can set the `pseudo` property on your story `parameters` to set a default value for each pseudo class. This makes it possible to test such states with [Chromatic](https://www.chromatic.com/).
17
17
 
18
18
  ### Limitations
19
19
 
@@ -70,3 +70,21 @@ Buttons.parameters = {
70
70
  ```
71
71
 
72
72
  This accepts a single CSS selector (string), or an array of CSS selectors on which to enable that pseudo style.
73
+
74
+ ### Overriding the default root element
75
+
76
+ By default, we use `#storybook-root` (or `#root` before Storybook 7) element as the root element for all pseudo classes. If you need to render elements outside Storybook's root element, you can set `parameters.pseudo.rootSelector` to override it. This is convenient for portals, dialogs, tooltips, etc.
77
+
78
+ For example, consider a `Dialog` component that inject itself to the document's `body` node:
79
+
80
+ ```jsx
81
+ export const DialogButton = () => (
82
+ <Dialog>
83
+ <Button>Hover</Button>
84
+ </Dialog>
85
+ )
86
+
87
+ DialogButton.parameters = {
88
+ pseudo: { hover: true, rootSelector: "body" },
89
+ }
90
+ ```
@@ -1,6 +1,7 @@
1
1
  // src/constants.ts
2
2
  var ADDON_ID = "storybook/pseudo-states";
3
3
  var TOOL_ID = `${ADDON_ID}/tool`;
4
+ var PARAM_KEY = "pseudo";
4
5
  var EXCLUDED_PSEUDO_ELEMENTS = ["::-webkit-scrollbar-thumb"];
5
6
  var PSEUDO_STATES = {
6
7
  hover: "hover",
@@ -16,6 +17,7 @@ var PSEUDO_STATES = {
16
17
  export {
17
18
  ADDON_ID,
18
19
  TOOL_ID,
20
+ PARAM_KEY,
19
21
  EXCLUDED_PSEUDO_ELEMENTS,
20
22
  PSEUDO_STATES
21
23
  };
package/dist/manager.js CHANGED
@@ -24,6 +24,7 @@ var import_manager_api2 = require("@storybook/manager-api");
24
24
  // src/constants.ts
25
25
  var ADDON_ID = "storybook/pseudo-states";
26
26
  var TOOL_ID = `${ADDON_ID}/tool`;
27
+ var PARAM_KEY = "pseudo";
27
28
  var PSEUDO_STATES = {
28
29
  hover: "hover",
29
30
  active: "active",
@@ -49,14 +50,25 @@ var LinkIcon = (0, import_theming.styled)(import_components.Icons)(({ active })
49
50
  }));
50
51
  var options = Object.keys(PSEUDO_STATES).sort();
51
52
  var PseudoStateTool = () => {
52
- const [{ pseudo }, updateGlobals] = (0, import_manager_api.useGlobals)();
53
- const isActive = (0, import_react.useCallback)((option) => {
54
- if (!pseudo)
55
- return false;
56
- return pseudo[option] === true;
57
- }, [pseudo]);
53
+ const [globals, updateGlobals] = (0, import_manager_api.useGlobals)();
54
+ const pseudo = globals[PARAM_KEY];
55
+ const isActive = (0, import_react.useCallback)(
56
+ (option) => {
57
+ if (!pseudo)
58
+ return false;
59
+ return pseudo[option] === true;
60
+ },
61
+ [pseudo]
62
+ );
58
63
  const toggleOption = (0, import_react.useCallback)(
59
- (option) => () => updateGlobals({ pseudo: { ...pseudo, [option]: !isActive(option) } }),
64
+ (option) => () => {
65
+ updateGlobals({
66
+ [PARAM_KEY]: {
67
+ ...pseudo,
68
+ [option]: !isActive(option)
69
+ }
70
+ });
71
+ },
60
72
  [pseudo]
61
73
  );
62
74
  return /* @__PURE__ */ import_react.default.createElement(
package/dist/manager.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  ADDON_ID,
3
+ PARAM_KEY,
3
4
  PSEUDO_STATES,
4
5
  TOOL_ID
5
- } from "./chunk-A7FPDXSC.mjs";
6
+ } from "./chunk-OZNRRAVT.mjs";
6
7
 
7
8
  // src/manager.ts
8
9
  import { addons, types } from "@storybook/manager-api";
@@ -21,14 +22,25 @@ var LinkIcon = styled(Icons)(({ active }) => ({
21
22
  }));
22
23
  var options = Object.keys(PSEUDO_STATES).sort();
23
24
  var PseudoStateTool = () => {
24
- const [{ pseudo }, updateGlobals] = useGlobals();
25
- const isActive = useCallback((option) => {
26
- if (!pseudo)
27
- return false;
28
- return pseudo[option] === true;
29
- }, [pseudo]);
25
+ const [globals, updateGlobals] = useGlobals();
26
+ const pseudo = globals[PARAM_KEY];
27
+ const isActive = useCallback(
28
+ (option) => {
29
+ if (!pseudo)
30
+ return false;
31
+ return pseudo[option] === true;
32
+ },
33
+ [pseudo]
34
+ );
30
35
  const toggleOption = useCallback(
31
- (option) => () => updateGlobals({ pseudo: { ...pseudo, [option]: !isActive(option) } }),
36
+ (option) => () => {
37
+ updateGlobals({
38
+ [PARAM_KEY]: {
39
+ ...pseudo,
40
+ [option]: !isActive(option)
41
+ }
42
+ });
43
+ },
32
44
  [pseudo]
33
45
  );
34
46
  return /* @__PURE__ */ React.createElement(
package/dist/preview.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  import * as _storybook_types from '@storybook/types';
2
2
 
3
3
  declare const decorators: _storybook_types.DecoratorFunction<_storybook_types.Renderer, _storybook_types.Args>[];
4
+ declare const globals: {
5
+ pseudo: boolean;
6
+ };
4
7
 
5
- export { decorators };
8
+ export { decorators, globals };
package/dist/preview.js CHANGED
@@ -20,17 +20,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/preview.ts
21
21
  var preview_exports = {};
22
22
  __export(preview_exports, {
23
- decorators: () => decorators
23
+ decorators: () => decorators,
24
+ globals: () => globals
24
25
  });
25
26
  module.exports = __toCommonJS(preview_exports);
26
27
 
27
- // src/preview/withPseudoState.ts
28
- var import_core_events = require("@storybook/core-events");
29
- var import_preview_api = require("@storybook/preview-api");
30
-
31
28
  // src/constants.ts
32
29
  var ADDON_ID = "storybook/pseudo-states";
33
30
  var TOOL_ID = `${ADDON_ID}/tool`;
31
+ var PARAM_KEY = "pseudo";
34
32
  var EXCLUDED_PSEUDO_ELEMENTS = ["::-webkit-scrollbar-thumb"];
35
33
  var PSEUDO_STATES = {
36
34
  hover: "hover",
@@ -43,6 +41,10 @@ var PSEUDO_STATES = {
43
41
  target: "target"
44
42
  };
45
43
 
44
+ // src/preview/withPseudoState.ts
45
+ var import_core_events = require("@storybook/core-events");
46
+ var import_preview_api = require("@storybook/preview-api");
47
+
46
48
  // src/preview/splitSelectors.ts
47
49
  var isAtRule = (selector) => selector.indexOf("@") === 0;
48
50
  var splitSelectors = (selectors) => {
@@ -156,7 +158,7 @@ var applyClasses = (element, classnames) => {
156
158
  Object.values(PSEUDO_STATES).forEach((state) => element.classList.remove(`pseudo-${state}`));
157
159
  classnames.forEach((classname) => element.classList.add(classname));
158
160
  };
159
- var applyParameter = (rootElement, parameter) => {
161
+ var applyParameter = (rootElement, parameter = {}) => {
160
162
  const map = /* @__PURE__ */ new Map([[rootElement, /* @__PURE__ */ new Set()]]);
161
163
  const add = (target, state) => map.set(target, /* @__PURE__ */ new Set([...map.get(target) || [], state]));
162
164
  Object.entries(parameter || {}).forEach(([state, value]) => {
@@ -184,31 +186,43 @@ var updateShadowHost = (shadowHost) => {
184
186
  }
185
187
  applyClasses(shadowHost, classnames);
186
188
  };
189
+ var pseudoConfig = (parameter) => {
190
+ const { rootSelector, ...pseudoStateConfig } = parameter || {};
191
+ return pseudoStateConfig;
192
+ };
193
+ var equals = (a = {}, b = {}) => a !== null && b !== null && Object.keys(a).length === Object.keys(b).length && Object.keys(a).every(
194
+ (key) => JSON.stringify(a[key]) === JSON.stringify(b[key])
195
+ );
187
196
  var withPseudoState = (StoryFn, { viewMode, parameters, id, globals: globalsArgs }) => {
188
197
  const { pseudo: parameter } = parameters;
189
- const { pseudo: globals } = globalsArgs;
190
- const canvasElement = (0, import_preview_api.useMemo)(() => {
198
+ const { pseudo: globals2 } = globalsArgs;
199
+ const { rootSelector } = parameter || {};
200
+ const rootElement = (0, import_preview_api.useMemo)(() => {
201
+ if (rootSelector) {
202
+ return document.querySelector(rootSelector);
203
+ }
191
204
  if (viewMode === "docs") {
192
205
  return document.getElementById(`story--${id}`);
193
206
  }
194
207
  return document.getElementById("storybook-root") || document.getElementById("root");
195
- }, [viewMode, id]);
208
+ }, [rootSelector, viewMode, id]);
196
209
  (0, import_preview_api.useEffect)(() => {
197
- if (parameter !== globals && viewMode === "story") {
210
+ const config = pseudoConfig(parameter);
211
+ if (viewMode === "story" && !equals(config, globals2)) {
198
212
  channel.emit(import_core_events.UPDATE_GLOBALS, {
199
- globals: { pseudo: parameter }
213
+ globals: { pseudo: config }
200
214
  });
201
215
  }
202
216
  }, [parameter, viewMode]);
203
217
  (0, import_preview_api.useEffect)(() => {
204
- if (!canvasElement)
218
+ if (!rootElement)
205
219
  return;
206
220
  const timeout = setTimeout(() => {
207
- applyParameter(canvasElement, globals || parameter);
221
+ applyParameter(rootElement, globals2 || pseudoConfig(parameter));
208
222
  shadowHosts.forEach(updateShadowHost);
209
223
  }, 0);
210
224
  return () => clearTimeout(timeout);
211
- }, [canvasElement, globals, parameter]);
225
+ }, [rootElement, globals2, parameter]);
212
226
  return StoryFn();
213
227
  };
214
228
  var rewriteStyleSheets = (shadowRoot) => {
@@ -234,7 +248,9 @@ if (Element.prototype.attachShadow) {
234
248
 
235
249
  // src/preview.ts
236
250
  var decorators = [withPseudoState];
251
+ var globals = { [PARAM_KEY]: false };
237
252
  // Annotate the CommonJS export names for ESM import in node:
238
253
  0 && (module.exports = {
239
- decorators
254
+ decorators,
255
+ globals
240
256
  });
package/dist/preview.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  EXCLUDED_PSEUDO_ELEMENTS,
3
+ PARAM_KEY,
3
4
  PSEUDO_STATES
4
- } from "./chunk-A7FPDXSC.mjs";
5
+ } from "./chunk-OZNRRAVT.mjs";
5
6
 
6
7
  // src/preview/withPseudoState.ts
7
8
  import {
@@ -125,7 +126,7 @@ var applyClasses = (element, classnames) => {
125
126
  Object.values(PSEUDO_STATES).forEach((state) => element.classList.remove(`pseudo-${state}`));
126
127
  classnames.forEach((classname) => element.classList.add(classname));
127
128
  };
128
- var applyParameter = (rootElement, parameter) => {
129
+ var applyParameter = (rootElement, parameter = {}) => {
129
130
  const map = /* @__PURE__ */ new Map([[rootElement, /* @__PURE__ */ new Set()]]);
130
131
  const add = (target, state) => map.set(target, /* @__PURE__ */ new Set([...map.get(target) || [], state]));
131
132
  Object.entries(parameter || {}).forEach(([state, value]) => {
@@ -153,31 +154,43 @@ var updateShadowHost = (shadowHost) => {
153
154
  }
154
155
  applyClasses(shadowHost, classnames);
155
156
  };
157
+ var pseudoConfig = (parameter) => {
158
+ const { rootSelector, ...pseudoStateConfig } = parameter || {};
159
+ return pseudoStateConfig;
160
+ };
161
+ var equals = (a = {}, b = {}) => a !== null && b !== null && Object.keys(a).length === Object.keys(b).length && Object.keys(a).every(
162
+ (key) => JSON.stringify(a[key]) === JSON.stringify(b[key])
163
+ );
156
164
  var withPseudoState = (StoryFn, { viewMode, parameters, id, globals: globalsArgs }) => {
157
165
  const { pseudo: parameter } = parameters;
158
- const { pseudo: globals } = globalsArgs;
159
- const canvasElement = useMemo(() => {
166
+ const { pseudo: globals2 } = globalsArgs;
167
+ const { rootSelector } = parameter || {};
168
+ const rootElement = useMemo(() => {
169
+ if (rootSelector) {
170
+ return document.querySelector(rootSelector);
171
+ }
160
172
  if (viewMode === "docs") {
161
173
  return document.getElementById(`story--${id}`);
162
174
  }
163
175
  return document.getElementById("storybook-root") || document.getElementById("root");
164
- }, [viewMode, id]);
176
+ }, [rootSelector, viewMode, id]);
165
177
  useEffect(() => {
166
- if (parameter !== globals && viewMode === "story") {
178
+ const config = pseudoConfig(parameter);
179
+ if (viewMode === "story" && !equals(config, globals2)) {
167
180
  channel.emit(UPDATE_GLOBALS, {
168
- globals: { pseudo: parameter }
181
+ globals: { pseudo: config }
169
182
  });
170
183
  }
171
184
  }, [parameter, viewMode]);
172
185
  useEffect(() => {
173
- if (!canvasElement)
186
+ if (!rootElement)
174
187
  return;
175
188
  const timeout = setTimeout(() => {
176
- applyParameter(canvasElement, globals || parameter);
189
+ applyParameter(rootElement, globals2 || pseudoConfig(parameter));
177
190
  shadowHosts.forEach(updateShadowHost);
178
191
  }, 0);
179
192
  return () => clearTimeout(timeout);
180
- }, [canvasElement, globals, parameter]);
193
+ }, [rootElement, globals2, parameter]);
181
194
  return StoryFn();
182
195
  };
183
196
  var rewriteStyleSheets = (shadowRoot) => {
@@ -203,6 +216,8 @@ if (Element.prototype.attachShadow) {
203
216
 
204
217
  // src/preview.ts
205
218
  var decorators = [withPseudoState];
219
+ var globals = { [PARAM_KEY]: false };
206
220
  export {
207
- decorators
221
+ decorators,
222
+ globals
208
223
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "storybook-addon-pseudo-states",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "CSS pseudo states for Storybook",
5
5
  "keywords": [
6
6
  "storybook-addons",