UNPKG

17.5 kBJavaScriptView Raw
1function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
2
3function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
4
5function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
6
7function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
8
9function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
10
11function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
12
13function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
14
15function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); }
16
17function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
18
19function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
20
21import "core-js/modules/es.weak-map.js";
22import "core-js/modules/es.object.to-string.js";
23import "core-js/modules/es.string.iterator.js";
24import "core-js/modules/es.array.iterator.js";
25import "core-js/modules/web.dom-collections.iterator.js";
26import "core-js/modules/es.set.js";
27import "core-js/modules/web.dom-collections.for-each.js";
28import "core-js/modules/es.array.includes.js";
29import "core-js/modules/es.string.includes.js";
30import "core-js/modules/es.function.name.js";
31import "core-js/modules/es.array.map.js";
32import "core-js/modules/es.array.concat.js";
33import "core-js/modules/es.object.entries.js";
34import "core-js/modules/es.object.keys.js";
35import "core-js/modules/es.symbol.js";
36import "core-js/modules/es.symbol.description.js";
37import "core-js/modules/es.symbol.iterator.js";
38import "core-js/modules/es.array.from.js";
39import "core-js/modules/es.array.slice.js";
40
41function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
42
43function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
44
45function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
46
47import global from 'global';
48import { logger } from '@storybook/client-logger';
49import { FORCE_RE_RENDER, STORY_RENDERED, UPDATE_STORY_ARGS, RESET_STORY_ARGS, UPDATE_GLOBALS } from '@storybook/core-events';
50import { addons } from './index';
51var globalWindow = global.window;
52export var HooksContext = /*#__PURE__*/function () {
53 function HooksContext() {
54 var _this = this;
55
56 _classCallCheck(this, HooksContext);
57
58 this.hookListsMap = void 0;
59 this.mountedDecorators = void 0;
60 this.prevMountedDecorators = void 0;
61 this.currentHooks = void 0;
62 this.nextHookIndex = void 0;
63 this.currentPhase = void 0;
64 this.currentEffects = void 0;
65 this.prevEffects = void 0;
66 this.currentDecoratorName = void 0;
67 this.hasUpdates = void 0;
68 this.currentContext = void 0;
69
70 this.renderListener = function (storyId) {
71 if (storyId !== _this.currentContext.id) return;
72
73 _this.triggerEffects();
74
75 _this.currentContext = null;
76
77 _this.removeRenderListeners();
78 };
79
80 this.init();
81 }
82
83 _createClass(HooksContext, [{
84 key: "init",
85 value: function init() {
86 this.hookListsMap = new WeakMap();
87 this.mountedDecorators = new Set();
88 this.prevMountedDecorators = this.mountedDecorators;
89 this.currentHooks = [];
90 this.nextHookIndex = 0;
91 this.currentPhase = 'NONE';
92 this.currentEffects = [];
93 this.prevEffects = [];
94 this.currentDecoratorName = null;
95 this.hasUpdates = false;
96 this.currentContext = null;
97 }
98 }, {
99 key: "clean",
100 value: function clean() {
101 this.prevEffects.forEach(function (effect) {
102 if (effect.destroy) {
103 effect.destroy();
104 }
105 });
106 this.init();
107 this.removeRenderListeners();
108 }
109 }, {
110 key: "getNextHook",
111 value: function getNextHook() {
112 var hook = this.currentHooks[this.nextHookIndex];
113 this.nextHookIndex += 1;
114 return hook;
115 }
116 }, {
117 key: "triggerEffects",
118 value: function triggerEffects() {
119 var _this2 = this;
120
121 // destroy removed effects
122 this.prevEffects.forEach(function (effect) {
123 if (!_this2.currentEffects.includes(effect) && effect.destroy) {
124 effect.destroy();
125 }
126 }); // trigger added effects
127
128 this.currentEffects.forEach(function (effect) {
129 if (!_this2.prevEffects.includes(effect)) {
130 // eslint-disable-next-line no-param-reassign
131 effect.destroy = effect.create();
132 }
133 });
134 this.prevEffects = this.currentEffects;
135 this.currentEffects = [];
136 }
137 }, {
138 key: "addRenderListeners",
139 value: function addRenderListeners() {
140 this.removeRenderListeners();
141 var channel = addons.getChannel();
142 channel.on(STORY_RENDERED, this.renderListener);
143 }
144 }, {
145 key: "removeRenderListeners",
146 value: function removeRenderListeners() {
147 var channel = addons.getChannel();
148 channel.removeListener(STORY_RENDERED, this.renderListener);
149 }
150 }]);
151
152 return HooksContext;
153}();
154
155function hookify(fn) {
156 return function () {
157 var _ref = typeof (arguments.length <= 0 ? undefined : arguments[0]) === 'function' ? arguments.length <= 1 ? undefined : arguments[1] : arguments.length <= 0 ? undefined : arguments[0],
158 hooks = _ref.hooks;
159
160 var prevPhase = hooks.currentPhase;
161 var prevHooks = hooks.currentHooks;
162 var prevNextHookIndex = hooks.nextHookIndex;
163 var prevDecoratorName = hooks.currentDecoratorName;
164 hooks.currentDecoratorName = fn.name;
165
166 if (hooks.prevMountedDecorators.has(fn)) {
167 hooks.currentPhase = 'UPDATE';
168 hooks.currentHooks = hooks.hookListsMap.get(fn) || [];
169 } else {
170 hooks.currentPhase = 'MOUNT';
171 hooks.currentHooks = [];
172 hooks.hookListsMap.set(fn, hooks.currentHooks);
173 hooks.prevMountedDecorators.add(fn);
174 }
175
176 hooks.nextHookIndex = 0;
177 var prevContext = globalWindow.STORYBOOK_HOOKS_CONTEXT;
178 globalWindow.STORYBOOK_HOOKS_CONTEXT = hooks;
179 var result = fn.apply(void 0, arguments);
180 globalWindow.STORYBOOK_HOOKS_CONTEXT = prevContext;
181
182 if (hooks.currentPhase === 'UPDATE' && hooks.getNextHook() != null) {
183 throw new Error('Rendered fewer hooks than expected. This may be caused by an accidental early return statement.');
184 }
185
186 hooks.currentPhase = prevPhase;
187 hooks.currentHooks = prevHooks;
188 hooks.nextHookIndex = prevNextHookIndex;
189 hooks.currentDecoratorName = prevDecoratorName;
190 return result;
191 };
192} // Counter to prevent infinite loops.
193
194
195var numberOfRenders = 0;
196var RENDER_LIMIT = 25;
197export var applyHooks = function applyHooks(applyDecorators) {
198 return function (storyFn, decorators) {
199 var decorated = applyDecorators(hookify(storyFn), decorators.map(function (decorator) {
200 return hookify(decorator);
201 }));
202 return function (context) {
203 var _ref2 = context,
204 hooks = _ref2.hooks;
205 hooks.prevMountedDecorators = hooks.mountedDecorators;
206 hooks.mountedDecorators = new Set([storyFn].concat(_toConsumableArray(decorators)));
207 hooks.currentContext = context;
208 hooks.hasUpdates = false;
209 var result = decorated(context);
210 numberOfRenders = 1;
211
212 while (hooks.hasUpdates) {
213 hooks.hasUpdates = false;
214 hooks.currentEffects = [];
215 result = decorated(context);
216 numberOfRenders += 1;
217
218 if (numberOfRenders > RENDER_LIMIT) {
219 throw new Error('Too many re-renders. Storybook limits the number of renders to prevent an infinite loop.');
220 }
221 }
222
223 hooks.addRenderListeners();
224 return result;
225 };
226 };
227};
228
229var areDepsEqual = function areDepsEqual(deps, nextDeps) {
230 return deps.length === nextDeps.length && deps.every(function (dep, i) {
231 return dep === nextDeps[i];
232 });
233};
234
235var invalidHooksError = function invalidHooksError() {
236 return new Error('Storybook preview hooks can only be called inside decorators and story functions.');
237};
238
239function getHooksContextOrNull() {
240 return globalWindow.STORYBOOK_HOOKS_CONTEXT || null;
241}
242
243function getHooksContextOrThrow() {
244 var hooks = getHooksContextOrNull();
245
246 if (hooks == null) {
247 throw invalidHooksError();
248 }
249
250 return hooks;
251}
252
253function useHook(name, callback, deps) {
254 var hooks = getHooksContextOrThrow();
255
256 if (hooks.currentPhase === 'MOUNT') {
257 if (deps != null && !Array.isArray(deps)) {
258 logger.warn("".concat(name, " received a final argument that is not an array (instead, received ").concat(deps, "). When specified, the final argument must be an array."));
259 }
260
261 var _hook = {
262 name: name,
263 deps: deps
264 };
265 hooks.currentHooks.push(_hook);
266 callback(_hook);
267 return _hook;
268 }
269
270 if (hooks.currentPhase === 'UPDATE') {
271 var _hook2 = hooks.getNextHook();
272
273 if (_hook2 == null) {
274 throw new Error('Rendered more hooks than during the previous render.');
275 }
276
277 if (_hook2.name !== name) {
278 logger.warn("Storybook has detected a change in the order of Hooks".concat(hooks.currentDecoratorName ? " called by ".concat(hooks.currentDecoratorName) : '', ". This will lead to bugs and errors if not fixed."));
279 }
280
281 if (deps != null && _hook2.deps == null) {
282 logger.warn("".concat(name, " received a final argument during this render, but not during the previous render. Even though the final argument is optional, its type cannot change between renders."));
283 }
284
285 if (deps != null && _hook2.deps != null && deps.length !== _hook2.deps.length) {
286 logger.warn("The final argument passed to ".concat(name, " changed size between renders. The order and size of this array must remain constant.\nPrevious: ").concat(_hook2.deps, "\nIncoming: ").concat(deps));
287 }
288
289 if (deps == null || _hook2.deps == null || !areDepsEqual(deps, _hook2.deps)) {
290 callback(_hook2);
291 _hook2.deps = deps;
292 }
293
294 return _hook2;
295 }
296
297 throw invalidHooksError();
298}
299
300function useMemoLike(name, nextCreate, deps) {
301 var _useHook = useHook(name, function (hook) {
302 // eslint-disable-next-line no-param-reassign
303 hook.memoizedState = nextCreate();
304 }, deps),
305 memoizedState = _useHook.memoizedState;
306
307 return memoizedState;
308}
309/* Returns a memoized value, see https://reactjs.org/docs/hooks-reference.html#usememo */
310
311
312export function useMemo(nextCreate, deps) {
313 return useMemoLike('useMemo', nextCreate, deps);
314}
315/* Returns a memoized callback, see https://reactjs.org/docs/hooks-reference.html#usecallback */
316
317export function useCallback(callback, deps) {
318 return useMemoLike('useCallback', function () {
319 return callback;
320 }, deps);
321}
322
323function useRefLike(name, initialValue) {
324 return useMemoLike(name, function () {
325 return {
326 current: initialValue
327 };
328 }, []);
329}
330/* Returns a mutable ref object, see https://reactjs.org/docs/hooks-reference.html#useref */
331
332
333export function useRef(initialValue) {
334 return useRefLike('useRef', initialValue);
335}
336
337function triggerUpdate() {
338 var hooks = getHooksContextOrNull(); // Rerun storyFn if updates were triggered synchronously, force rerender otherwise
339
340 if (hooks != null && hooks.currentPhase !== 'NONE') {
341 hooks.hasUpdates = true;
342 } else {
343 try {
344 addons.getChannel().emit(FORCE_RE_RENDER);
345 } catch (e) {
346 logger.warn('State updates of Storybook preview hooks work only in browser');
347 }
348 }
349}
350
351function useStateLike(name, initialState) {
352 var stateRef = useRefLike(name, // @ts-ignore S type should never be function, but there's no way to tell that to TypeScript
353 typeof initialState === 'function' ? initialState() : initialState);
354
355 var setState = function setState(update) {
356 // @ts-ignore S type should never be function, but there's no way to tell that to TypeScript
357 stateRef.current = typeof update === 'function' ? update(stateRef.current) : update;
358 triggerUpdate();
359 };
360
361 return [stateRef.current, setState];
362}
363/* Returns a stateful value, and a function to update it, see https://reactjs.org/docs/hooks-reference.html#usestate */
364
365
366export function useState(initialState) {
367 return useStateLike('useState', initialState);
368}
369/* A redux-like alternative to useState, see https://reactjs.org/docs/hooks-reference.html#usereducer */
370
371export function useReducer(reducer, initialArg, init) {
372 var initialState = init != null ? function () {
373 return init(initialArg);
374 } : initialArg;
375
376 var _useStateLike = useStateLike('useReducer', initialState),
377 _useStateLike2 = _slicedToArray(_useStateLike, 2),
378 state = _useStateLike2[0],
379 setState = _useStateLike2[1];
380
381 var dispatch = function dispatch(action) {
382 return setState(function (prevState) {
383 return reducer(prevState, action);
384 });
385 };
386
387 return [state, dispatch];
388}
389/*
390 Triggers a side effect, see https://reactjs.org/docs/hooks-reference.html#usestate
391 Effects are triggered synchronously after rendering the story
392*/
393
394export function useEffect(create, deps) {
395 var hooks = getHooksContextOrThrow();
396 var effect = useMemoLike('useEffect', function () {
397 return {
398 create: create
399 };
400 }, deps);
401
402 if (!hooks.currentEffects.includes(effect)) {
403 hooks.currentEffects.push(effect);
404 }
405}
406
407/* Accepts a map of Storybook channel event listeners, returns an emit function */
408export function useChannel(eventMap) {
409 var deps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
410 var channel = addons.getChannel();
411 useEffect(function () {
412 Object.entries(eventMap).forEach(function (_ref3) {
413 var _ref4 = _slicedToArray(_ref3, 2),
414 type = _ref4[0],
415 listener = _ref4[1];
416
417 return channel.on(type, listener);
418 });
419 return function () {
420 Object.entries(eventMap).forEach(function (_ref5) {
421 var _ref6 = _slicedToArray(_ref5, 2),
422 type = _ref6[0],
423 listener = _ref6[1];
424
425 return channel.removeListener(type, listener);
426 });
427 };
428 }, [].concat(_toConsumableArray(Object.keys(eventMap)), _toConsumableArray(deps)));
429 return useCallback(channel.emit.bind(channel), [channel]);
430}
431/* Returns current story context */
432
433export function useStoryContext() {
434 var _getHooksContextOrThr = getHooksContextOrThrow(),
435 currentContext = _getHooksContextOrThr.currentContext;
436
437 if (currentContext == null) {
438 throw invalidHooksError();
439 }
440
441 return currentContext;
442}
443/* Returns current value of a story parameter */
444
445export function useParameter(parameterKey, defaultValue) {
446 var _useStoryContext = useStoryContext(),
447 parameters = _useStoryContext.parameters;
448
449 if (parameterKey) {
450 var _parameters$parameter;
451
452 return (_parameters$parameter = parameters[parameterKey]) !== null && _parameters$parameter !== void 0 ? _parameters$parameter : defaultValue;
453 }
454
455 return undefined;
456}
457/* Returns current value of story args */
458
459export function useArgs() {
460 var channel = addons.getChannel();
461
462 var _useStoryContext2 = useStoryContext(),
463 storyId = _useStoryContext2.id,
464 args = _useStoryContext2.args;
465
466 var updateArgs = useCallback(function (updatedArgs) {
467 return channel.emit(UPDATE_STORY_ARGS, {
468 storyId: storyId,
469 updatedArgs: updatedArgs
470 });
471 }, [channel, storyId]);
472 var resetArgs = useCallback(function (argNames) {
473 return channel.emit(RESET_STORY_ARGS, {
474 storyId: storyId,
475 argNames: argNames
476 });
477 }, [channel, storyId]);
478 return [args, updateArgs, resetArgs];
479}
480/* Returns current value of global args */
481
482export function useGlobals() {
483 var channel = addons.getChannel();
484
485 var _useStoryContext3 = useStoryContext(),
486 globals = _useStoryContext3.globals;
487
488 var updateGlobals = useCallback(function (newGlobals) {
489 return channel.emit(UPDATE_GLOBALS, {
490 globals: newGlobals
491 });
492 }, [channel]);
493 return [globals, updateGlobals];
494}
\No newline at end of file