@khanacademy/wonder-blocks-core 4.2.0 → 4.3.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @khanacademy/wonder-blocks-core
2
2
 
3
+ ## 4.3.1
4
+
5
+ ## 4.3.0
6
+
7
+ ### Minor Changes
8
+
9
+ - 246a921d: NEW: `useForceUpdate` hook. This should rarely be used and likely only ever from other hooks.
10
+
11
+ ## 4.2.1
12
+
3
13
  ## 4.2.0
4
14
 
5
15
  ### Minor Changes
package/dist/es/index.js CHANGED
@@ -29,24 +29,17 @@ function processStyleList(style) {
29
29
  style: {},
30
30
  className: ""
31
31
  };
32
- } // Check to see if we should inline all the styles for snapshot tests.
33
-
32
+ }
34
33
 
35
34
  const shouldInlineStyles = typeof global !== "undefined" && global.SNAPSHOT_INLINE_APHRODITE;
36
35
  flatten(style).forEach(child => {
37
- // Check for aphrodite internal property
38
36
  const _definition = child._definition;
39
37
 
40
38
  if (_definition != null) {
41
39
  if (shouldInlineStyles) {
42
- const def = {}; // React 16 complains about invalid keys in inline styles.
43
- // It doesn't accept kebab-case in media queries and instead
44
- // prefers camelCase.
40
+ const def = {};
45
41
 
46
42
  for (const [key, value] of Object.entries(_definition)) {
47
- // This regex converts all instances of -{lowercaseLetter}
48
- // to the uppercase version of that letter, without the
49
- // leading dash.
50
43
  def[key.replace(/-[a-z]/g, match => match[1].toUpperCase())] = value;
51
44
  }
52
45
 
@@ -58,11 +51,7 @@ function processStyleList(style) {
58
51
  inlineStyles.push(child);
59
52
  }
60
53
  });
61
- const inlineStylesObject = Object.assign.apply(Object, [{}].concat(inlineStyles)); // TODO(somewhatabstract): When aphrodite no longer puts "!important" on
62
- // all the styles, remove this <ADD JIRA ISSUE HERE IF THIS PASSES REVIEW>
63
- // If we're not snapshotting styles, let's create a class for the inline
64
- // styles so that they can apply to the element even with aphrodite's
65
- // use of !important.
54
+ const inlineStylesObject = Object.assign.apply(Object, [{}].concat(inlineStyles));
66
55
 
67
56
  if (inlineStyles.length > 0 && !shouldInlineStyles) {
68
57
  const inlineStylesStyleSheet = StyleSheet.create({
@@ -81,32 +70,14 @@ const _excluded$2 = ["children", "style", "tag", "testId"];
81
70
  const isHeaderRegex = /^h[1-6]$/;
82
71
  const styles$1 = StyleSheet.create({
83
72
  text: {
84
- // Disable subpixel antialiasing on Mac desktop for consistency of
85
- // rendering with mobile and Sketch (neither of which support it).
86
- // See https://bjango.com/articles/subpixeltext/ for more details.
87
73
  WebkitFontSmoothing: "antialiased",
88
74
  MozOsxFontSmoothing: "grayscale"
89
75
  },
90
76
  header: {
91
- // User agent stylesheets add vertical margins to header tags by
92
- // default. We prefer to be more deliberate in our spacing instead.
93
77
  marginTop: 0,
94
78
  marginBottom: 0
95
79
  }
96
80
  });
97
- /**
98
- * Text is a building block for constructing other components. `Text` roughly
99
- * maps to `span`. You can override which tag is used to render the component
100
- * (for semantic purposes) by specifying the `tag` prop.
101
- *
102
- * These components can take styles (via the `style` prop) in a variety of
103
- * manners:
104
- *
105
- * - An inline style object
106
- * - An `aphrodite` StyleSheet style
107
- * - An array combining the above
108
- */
109
-
110
81
  class Text extends React.Component {
111
82
  render() {
112
83
  const _this$props = this.props,
@@ -120,7 +91,7 @@ class Text extends React.Component {
120
91
 
121
92
  const isHeader = isHeaderRegex.test(Tag);
122
93
  const styleAttributes = processStyleList([styles$1.text, isHeader && styles$1.header, style]);
123
- return /*#__PURE__*/React.createElement(Tag, _extends({}, otherProps, {
94
+ return React.createElement(Tag, _extends({}, otherProps, {
124
95
  style: styleAttributes.style,
125
96
  className: styleAttributes.className,
126
97
  "data-test-id": testId
@@ -133,16 +104,13 @@ Text.defaultProps = {
133
104
  };
134
105
 
135
106
  const _excluded$1 = ["className", "style"];
136
- // TODO(kevinb): have an a version which uses exact object types
137
107
  function addStyle(Component, defaultStyle) {
138
108
  function StyleComponent(props) {
139
109
  const {
140
110
  className,
141
111
  style
142
112
  } = props,
143
- tmpOtherProps = _objectWithoutPropertiesLoose(props, _excluded$1); // NOTE(jeresig): We need to cast the remaining props to be the right
144
- // value to ensure that they're typed properly.
145
-
113
+ tmpOtherProps = _objectWithoutPropertiesLoose(props, _excluded$1);
146
114
 
147
115
  const otherProps = tmpOtherProps;
148
116
  const reset = typeof Component === "string" ? overrides[Component] : null;
@@ -150,7 +118,7 @@ function addStyle(Component, defaultStyle) {
150
118
  className: aphroditeClassName,
151
119
  style: inlineStyles
152
120
  } = processStyleList([reset, defaultStyle, style]);
153
- return /*#__PURE__*/React.createElement(Component, _extends({}, otherProps, {
121
+ return React.createElement(Component, _extends({}, otherProps, {
154
122
  className: [aphroditeClassName, className].filter(Boolean).join(" "),
155
123
  style: inlineStyles
156
124
  }));
@@ -158,26 +126,17 @@ function addStyle(Component, defaultStyle) {
158
126
 
159
127
  return StyleComponent;
160
128
  }
161
- /**
162
- * These are necessary to override various custom styles that browsers add so that
163
- * elements have consistent styles across all browsers. Only add styles here if
164
- * they appear in https://github.com/necolas/normalize.css/blob/master/normalize.css.
165
- */
166
-
167
129
  const overrides = StyleSheet.create({
168
130
  button: {
169
131
  margin: 0,
170
- // Safari adds 2px left/right margins
171
132
  "::-moz-focus-inner": {
172
- border: 0 // Firefox adds an inner focus ring around text
173
-
133
+ border: 0
174
134
  }
175
135
  }
176
136
  });
177
137
 
178
138
  const _excluded = ["testId", "tag"];
179
139
  const styles = StyleSheet.create({
180
- // https://github.com/facebook/css-layout#default-values
181
140
  default: {
182
141
  alignItems: "stretch",
183
142
  borderWidth: 0,
@@ -189,7 +148,6 @@ const styles = StyleSheet.create({
189
148
  padding: 0,
190
149
  position: "relative",
191
150
  zIndex: 0,
192
- // fix flexbox bugs
193
151
  minHeight: 0,
194
152
  minWidth: 0
195
153
  }
@@ -199,20 +157,6 @@ const StyledArticle = addStyle("article", styles.default);
199
157
  const StyledAside = addStyle("aside", styles.default);
200
158
  const StyledNav = addStyle("nav", styles.default);
201
159
  const StyledSection = addStyle("section", styles.default);
202
- /**
203
- * View is a building block for constructing other components. `View` roughly
204
- * maps to `div` and `Text` roughly maps to `span`. You can override which tag
205
- * is used to render the component (for semantic purposes) by specifying the
206
- * `tag` prop.
207
- *
208
- * These components can take styles (via the `style` prop) in a variety of
209
- * manners:
210
- *
211
- * - An inline style object
212
- * - An `aphrodite` StyleSheet style
213
- * - An array combining the above
214
- */
215
-
216
160
  class View extends React.Component {
217
161
  render() {
218
162
  const _this$props = this.props,
@@ -228,19 +172,19 @@ class View extends React.Component {
228
172
 
229
173
  switch (tag) {
230
174
  case "article":
231
- return /*#__PURE__*/React.createElement(StyledArticle, props);
175
+ return React.createElement(StyledArticle, props);
232
176
 
233
177
  case "aside":
234
- return /*#__PURE__*/React.createElement(StyledAside, props);
178
+ return React.createElement(StyledAside, props);
235
179
 
236
180
  case "nav":
237
- return /*#__PURE__*/React.createElement(StyledNav, props);
181
+ return React.createElement(StyledNav, props);
238
182
 
239
183
  case "section":
240
- return /*#__PURE__*/React.createElement(StyledSection, props);
184
+ return React.createElement(StyledSection, props);
241
185
 
242
186
  case "div":
243
- return /*#__PURE__*/React.createElement(StyledDiv, props);
187
+ return React.createElement(StyledDiv, props);
244
188
 
245
189
  default:
246
190
  throw Error(`${tag} is not an allowed value for the 'tag' prop`);
@@ -257,53 +201,8 @@ const RenderState = require("flow-enums-runtime")({
257
201
  Initial: "initial",
258
202
  Standard: "standard"
259
203
  });
260
- /**
261
- * This is the context that tracks who is doing what in our SSR component tree.
262
- *
263
- * root:
264
- * no one has instigated an initial SSR render so the component that sees
265
- * this "root" state is responsible for controlling initial versus standard
266
- * rendering semantics
267
- *
268
- * initial:
269
- * this means the SSR render has started, and all SSR components should act
270
- * as though they are on the server
271
- *
272
- * standard:
273
- * means that we're all now doing non-SSR rendering
274
- */
275
-
276
- const RenderStateContext = /*#__PURE__*/React.createContext(RenderState.Root);
277
-
278
- /**
279
- * We use render functions so that we don't do any work unless we need to.
280
- * This avoids rendering but not mounting potentially complex component trees.
281
- */
282
-
283
- /**
284
- * Defer or change rendering until the component did mount.
285
- *
286
- * The purpose of this component is to disable or modify serverside rendering
287
- * of certain components. Disabling rendering on the server, by itself, would
288
- * not be sufficient, since the initial render of the component must match
289
- * what is rendered on the server. Therefore, this component also disables
290
- * rendering the first time around on the client.
291
- *
292
- * If `WithSSRPlaceholder` components are nested within one another,
293
- * the root `WithSSRPlaceholder` component will handle the initial
294
- * render, but nested `WithSSRPlaceholder` components will delegate to
295
- * the root one, meaning that we don't cascade delayed rendering down
296
- * the component tree. This will also be the case across portal
297
- * boundaries.
298
- *
299
- * Example:
300
- *
301
- * ```js
302
- * <WithSSRPlaceholder placeholder={() => <div>Renders on the server!</div>}>
303
- * {() => <div>Only renders on the client (after rehydration).</div>}
304
- * </WithSSRPlaceholder>
305
- * ```
306
- */
204
+ const RenderStateContext = React.createContext(RenderState.Root);
205
+
307
206
  class WithSSRPlaceholder extends React.Component {
308
207
  constructor(...args) {
309
208
  super(...args);
@@ -315,9 +214,6 @@ class WithSSRPlaceholder extends React.Component {
315
214
 
316
215
  componentDidMount() {
317
216
  if (this._isTheRootComponent) {
318
- // We only want to force a new render if we were responsible for
319
- // the first render, so we guard that state change here.
320
- // eslint-disable-next-line react/no-did-mount-set-state
321
217
  this.setState({
322
218
  mounted: true
323
219
  });
@@ -331,30 +227,20 @@ class WithSSRPlaceholder extends React.Component {
331
227
  const {
332
228
  children,
333
229
  placeholder
334
- } = this.props; // We are the first component in the tree.
335
- // We are in control of instigating a second render for our
336
- // component tree.
337
-
230
+ } = this.props;
338
231
  this._isTheRootComponent = true;
339
232
 
340
233
  if (mounted) {
341
- // This is our second non-SSR render, so let's tell everyone to
342
- // do their thing.
343
- return /*#__PURE__*/React.createElement(RenderStateContext.Provider, {
234
+ return React.createElement(RenderStateContext.Provider, {
344
235
  value: RenderState.Standard
345
236
  }, children());
346
- } // OK, this is the very first render.
347
- // If we have a placeholder, we render it, and ensure that any
348
- // nested SSR components know we're still on that first render
349
- // but they're not in charge of instigating the second render.
350
-
237
+ }
351
238
 
352
239
  if (placeholder) {
353
- return /*#__PURE__*/React.createElement(RenderStateContext.Provider, {
240
+ return React.createElement(RenderStateContext.Provider, {
354
241
  value: RenderState.Initial
355
242
  }, placeholder());
356
- } // Otherwise, we return nothing.
357
-
243
+ }
358
244
 
359
245
  return null;
360
246
  }
@@ -370,71 +256,29 @@ class WithSSRPlaceholder extends React.Component {
370
256
  return this._renderAsRootComponent();
371
257
 
372
258
  case RenderState.Initial:
373
- // We're not the root component, so we just have to either
374
- // render our placeholder or nothing.
375
- // The second render is going to be triggered for us.
376
259
  if (placeholder) {
377
260
  return placeholder();
378
- } // Otherwise, we render nothing.
379
-
261
+ }
380
262
 
381
263
  return null;
382
264
 
383
265
  case RenderState.Standard:
384
- // We have covered the SSR render, we're now rendering with
385
- // standard rendering semantics.
386
266
  return children();
387
- } // There are edge cases where for some reason, we get an unknown
388
- // context value here. So far it seems to be when we're nested in a
389
- // v1 WithSSRPlaceholder equivalent component, or in some older
390
- // React v16 situations where we're nested in the provider of a
391
- // different context.
392
- //
393
- // We ignore this from coverage. It's a maintenance case to help
394
- // us catch code changes that affect the control flow unexpectedly,
395
- // but it's not something we need to write a test case for.
396
- //
397
- // Flow will assert exhaustiveness of the switch because Flow enums
398
- // rock.
399
- //
400
-
401
- /* istanbul ignore next */
402
-
267
+ }
403
268
 
404
269
  {
405
- // Let's log this case so we can debug it easily.
406
- // Then fall through to the root case.
407
-
408
- /* eslint-disable-next-line no-console */
409
- console.log(`We got a render state we don't understand: "${JSON.stringify(renderState)}"`); // We "fallthrough" to the root case. This is more obvious
410
- // and maintainable code than just ignoring the no-fallthrough
411
- // lint rule.
412
-
270
+ console.log(`We got a render state we don't understand: "${JSON.stringify(renderState)}"`);
413
271
  return this._maybeRender(RenderState.Root);
414
272
  }
415
273
  }
416
274
 
417
275
  render() {
418
- return /*#__PURE__*/React.createElement(RenderStateContext.Consumer, null, value => this._maybeRender(value));
276
+ return React.createElement(RenderStateContext.Consumer, null, value => this._maybeRender(value));
419
277
  }
420
278
 
421
279
  }
422
280
 
423
- /**
424
- * This is NOT for direct use. Instead, see the UniqueIDProvider component.
425
- *
426
- * Implements IIdentifierFactory to provide unique identifiers.
427
- */
428
281
  class UniqueIDFactory {
429
- /**
430
- * Creates a UniqueIDFactory instance.
431
- *
432
- * @param {string} scope An optional case-insensitive scope for the
433
- * factory. This will be used as part of the identifier. Useful for
434
- * providing context to the identifiers, which can be useful in
435
- * differentiating elements when debugging the DOM. This must contain only
436
- * hyphen and alphanumeric characters.
437
- */
438
282
  constructor(scope) {
439
283
  this.get = key => {
440
284
  const normalizedKey = key.toLowerCase();
@@ -455,13 +299,6 @@ class UniqueIDFactory {
455
299
 
456
300
  this._uniqueFactoryName = `uid-${normalizedScope}-${UniqueIDFactory._factoryUniquenessCounter++}`;
457
301
  }
458
- /**
459
- * This method verifies that a string contains valid characters for an
460
- * identifier. It does not assert that a string IS a valid identifier (for
461
- * example, that it doesn't start with numbers). We don't need to do that
462
- * here because all identifiers are prefixed to avoid needing that check.
463
- */
464
-
465
302
 
466
303
  _hasValidIdChars(value) {
467
304
  if (typeof value !== "string") {
@@ -471,30 +308,10 @@ class UniqueIDFactory {
471
308
  const invalidCharsReplaced = value.replace(/[^\d\w-]/g, "-");
472
309
  return value === invalidCharsReplaced;
473
310
  }
474
- /**
475
- * Provides a unique identifier with the given key.
476
- *
477
- * @param {string} key The case-insensitive key of the identifier.
478
- *
479
- * @returns {string} A unique identifier that will remain the same for this
480
- * key in this factory. This must contain only hyphen and alphanumeric
481
- * characters.
482
- */
483
-
484
311
 
485
312
  }
486
313
  UniqueIDFactory._factoryUniquenessCounter = 0;
487
314
 
488
- /**
489
- * This is NOT for direct use. Instead, see the UniqueIDProvider component.
490
- *
491
- * Implements a version of IIdentifierFactory that can be used for providing
492
- * identifiers on initial render of components that are eligible for server-side
493
- * rendering.
494
- *
495
- * The identifiers are not guaranteed to be unique, but they will match between
496
- * server and the first client render.
497
- */
498
315
  class SsrIDFactory {
499
316
  get(id) {
500
317
  return id;
@@ -505,76 +322,37 @@ class SsrIDFactory {
505
322
  SsrIDFactory.Default = new SsrIDFactory();
506
323
  var SsrIDFactory$1 = SsrIDFactory.Default;
507
324
 
508
- /**
509
- * The `UniqueIDProvider` component is how Wonder Blocks components obtain
510
- * unique identifiers. This component ensures that server-side rendering and
511
- * initial client rendering match while allowing the provision of unique
512
- * identifiers for the client.
513
- *
514
- * In all but the first render, the children are rendered with the same
515
- * `IIdentifierFactory` instance, ensuring that the same calls will return the
516
- * same identifiers.
517
- *
518
- * The `get` method of the identifier factory ensures that the same identifier
519
- * is returned for like requests, but also that all identifiers provided are
520
- * unique. Therefore, `get("test")` will always equal `get("test")`, and
521
- * `get("test2")` will always equal `get("test2")`, but `get("test")` will
522
- * never equal `get("test2")`.
523
- */
524
325
  class UniqueIDProvider extends React.Component {
525
326
  _performRender(firstRender) {
526
327
  const {
527
328
  children,
528
329
  mockOnFirstRender,
529
330
  scope
530
- } = this.props; // If this is our first render, we're going to stop right here.
531
- // Note: `firstRender` will be `false` on the first render if this
532
- // component is a descendant of a `WithSSRPlaceholder`.
331
+ } = this.props;
533
332
 
534
333
  if (firstRender) {
535
334
  if (mockOnFirstRender) {
536
- // We're allowing an initial render, so let's pass our mock
537
- // identifier factory to support SSR.
538
335
  return children(SsrIDFactory$1);
539
336
  }
540
337
 
541
338
  return null;
542
- } // Create an identifier factory if we don't already have one
543
-
339
+ }
544
340
 
545
341
  if (!this._idFactory) {
546
342
  this._idFactory = new UniqueIDFactory(scope);
547
- } // It's a regular render, so let's use our identifier factory.
548
-
343
+ }
549
344
 
550
345
  return children(this._idFactory);
551
346
  }
552
347
 
553
348
  render() {
554
- // Here we use the WithSSRPlaceholder component to control
555
- // when we render and whether we provide a mock or real
556
- // identifier factory.
557
- return /*#__PURE__*/React.createElement(WithSSRPlaceholder, {
349
+ return React.createElement(WithSSRPlaceholder, {
558
350
  placeholder: () => this._performRender(true)
559
351
  }, () => this._performRender(false));
560
352
  }
561
353
 
562
354
  }
563
355
 
564
- /**
565
- * This is a wrapper that returns an identifier. If the `id` prop is set, the component will
566
- * return the same id to be consumed by its children. Otherwise, a unique id will be provided.
567
- * This is beneficial for accessibility purposes, among other things.
568
- *
569
- * The main difference with UniqueIDProvider is that IDProvider has a single responsibility,
570
- * to return an identifier that can by used by the children that are rendered internally.
571
- *
572
- * This way, the wrapped component will receive this custom ID and will use it to connect
573
- * different elements.
574
- *
575
- * e.g. It uses the same generated id to connect a Dialog with its main title, or form label
576
- * with the associated input element, etc.
577
- */
578
356
  class IDProvider extends React.Component {
579
357
  renderChildren(ids) {
580
358
  const {
@@ -597,11 +375,9 @@ class IDProvider extends React.Component {
597
375
  } = this.props;
598
376
 
599
377
  if (id) {
600
- // Let's bypass the extra weight of an id provider since we don't
601
- // need it.
602
378
  return this.renderChildren();
603
379
  } else {
604
- return /*#__PURE__*/React.createElement(UniqueIDProvider, {
380
+ return React.createElement(UniqueIDProvider, {
605
381
  scope: scope,
606
382
  mockOnFirstRender: true
607
383
  }, ids => this.renderChildren(ids));
@@ -613,30 +389,12 @@ IDProvider.defaultId = "wb-id";
613
389
 
614
390
  let serverSide = false;
615
391
  var server = {
616
- /**
617
- * Check if we are running in server-side mode.
618
- *
619
- * @returns {boolean} `true` if we are in server-side mode; otherwise,
620
- * `false`
621
- */
622
392
  isServerSide: () => serverSide,
623
-
624
- /**
625
- * Set server-side mode to true.
626
- */
627
393
  setServerSide: () => {
628
394
  serverSide = true;
629
395
  }
630
396
  };
631
397
 
632
- /**
633
- * Returns a unique identifier factory. If the parent component hasn't
634
- * been mounted yet, the global SsrIDFactory will be returned until the
635
- * component becomes mounted.
636
- *
637
- * @param {string} [scope] optional string to prefix generated ids with.
638
- * @returns {IIdentifierFactory}
639
- */
640
398
  const useUniqueIdWithMock = scope => {
641
399
  const renderState = useContext$1(RenderStateContext);
642
400
  const idFactory = useRef(null);
@@ -655,14 +413,6 @@ const useUniqueIdWithMock = scope => {
655
413
 
656
414
  return idFactory.current;
657
415
  };
658
- /**
659
- * Returns a unique identifier factory. If the parent component hasn't
660
- * been mounted yet, null will be returned.
661
- *
662
- * @param {string} [scope] optional string to prefix generated ids with.
663
- * @returns {?IIdentifierFactory}
664
- */
665
-
666
416
  const useUniqueIdWithoutMock = scope => {
667
417
  const renderState = useContext$1(RenderStateContext);
668
418
  const idFactory = useRef(null);
@@ -682,6 +432,12 @@ const useUniqueIdWithoutMock = scope => {
682
432
  return idFactory.current;
683
433
  };
684
434
 
435
+ const useForceUpdate = () => {
436
+ const [, setState] = React.useState(false);
437
+ const forceUpdate = React.useCallback(() => setState(state => !state), []);
438
+ return forceUpdate;
439
+ };
440
+
685
441
  const {
686
442
  useContext,
687
443
  useEffect,
@@ -695,20 +451,18 @@ const RenderStateRoot = ({
695
451
  const contextValue = useContext(RenderStateContext);
696
452
  useEffect(() => {
697
453
  setFirstRender(false);
698
- }, []); // This effect will only run once.
454
+ }, []);
699
455
 
700
456
  if (contextValue !== RenderState.Root) {
701
457
  if (throwIfNested) {
702
458
  throw new Error("There's already a <RenderStateRoot> above this instance in " + "the render tree. This instance should be removed.");
703
- } // Avoid rendering multiple providers if this RenderStateRoot
704
- // is nested inside another one.
705
-
459
+ }
706
460
 
707
461
  return children;
708
462
  }
709
463
 
710
464
  const value = firstRender ? RenderState.Initial : RenderState.Standard;
711
- return /*#__PURE__*/React.createElement(RenderStateContext.Provider, {
465
+ return React.createElement(RenderStateContext.Provider, {
712
466
  value: value
713
467
  }, children);
714
468
  };
@@ -716,4 +470,4 @@ RenderStateRoot.defaultProps = {
716
470
  throwIfNested: true
717
471
  };
718
472
 
719
- export { IDProvider, RenderStateRoot, server as Server, Text, UniqueIDProvider, View, WithSSRPlaceholder, addStyle, useUniqueIdWithMock, useUniqueIdWithoutMock };
473
+ export { IDProvider, RenderStateRoot, server as Server, Text, UniqueIDProvider, View, WithSSRPlaceholder, addStyle, useForceUpdate, useUniqueIdWithMock, useUniqueIdWithoutMock };
package/dist/index.js CHANGED
@@ -82,7 +82,7 @@ module.exports =
82
82
  /******/
83
83
  /******/
84
84
  /******/ // Load entry module and return exports
85
- /******/ return __webpack_require__(__webpack_require__.s = 15);
85
+ /******/ return __webpack_require__(__webpack_require__.s = 16);
86
86
  /******/ })
87
87
  /************************************************************************/
88
88
  /******/ ([
@@ -101,7 +101,7 @@ module.exports = require("react");
101
101
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
102
102
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
103
103
 
104
- const RenderState = __webpack_require__(17)({
104
+ const RenderState = __webpack_require__(18)({
105
105
  Root: "root",
106
106
  Initial: "initial",
107
107
  Standard: "standard"
@@ -573,7 +573,7 @@ function processStyleList(style) {
573
573
  className: aphrodite__WEBPACK_IMPORTED_MODULE_0__["css"].apply(void 0, stylesheetStyles)
574
574
  };
575
575
  }
576
- /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(16)))
576
+ /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(17)))
577
577
 
578
578
  /***/ }),
579
579
  /* 8 */
@@ -917,6 +917,34 @@ let serverSide = false;
917
917
  /* 14 */
918
918
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
919
919
 
920
+ "use strict";
921
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return useForceUpdate; });
922
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
923
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
924
+
925
+ /**
926
+ * Hook for forcing a component to update on demand.
927
+ *
928
+ * This is for use inside other hooks that do some advanced
929
+ * trickery with storing state outside of React's own state
930
+ * mechanisms. As such this should never be called directly
931
+ * outside of a hook, and more often than not, is the wrong
932
+ * choice for whatever you are trying to do. If in doubt,
933
+ * don't use it.
934
+ *
935
+ * @returns {() => void} A function that forces the component to update.
936
+ */
937
+
938
+ const useForceUpdate = () => {
939
+ const [, setState] = react__WEBPACK_IMPORTED_MODULE_0__["useState"](false);
940
+ const forceUpdate = react__WEBPACK_IMPORTED_MODULE_0__["useCallback"](() => setState(state => !state), []);
941
+ return forceUpdate;
942
+ };
943
+
944
+ /***/ }),
945
+ /* 15 */
946
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
947
+
920
948
  "use strict";
921
949
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return RenderStateRoot; });
922
950
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
@@ -963,7 +991,7 @@ RenderStateRoot.defaultProps = {
963
991
  };
964
992
 
965
993
  /***/ }),
966
- /* 15 */
994
+ /* 16 */
967
995
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
968
996
 
969
997
  "use strict";
@@ -994,8 +1022,12 @@ __webpack_require__.r(__webpack_exports__);
994
1022
 
995
1023
  /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useUniqueIdWithoutMock", function() { return _hooks_use_unique_id_js__WEBPACK_IMPORTED_MODULE_7__["b"]; });
996
1024
 
997
- /* harmony import */ var _components_render_state_root_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(14);
998
- /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "RenderStateRoot", function() { return _components_render_state_root_js__WEBPACK_IMPORTED_MODULE_8__["a"]; });
1025
+ /* harmony import */ var _hooks_use_force_update_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(14);
1026
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useForceUpdate", function() { return _hooks_use_force_update_js__WEBPACK_IMPORTED_MODULE_8__["a"]; });
1027
+
1028
+ /* harmony import */ var _components_render_state_root_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(15);
1029
+ /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "RenderStateRoot", function() { return _components_render_state_root_js__WEBPACK_IMPORTED_MODULE_9__["a"]; });
1030
+
999
1031
 
1000
1032
 
1001
1033
 
@@ -1008,7 +1040,7 @@ __webpack_require__.r(__webpack_exports__);
1008
1040
 
1009
1041
 
1010
1042
  /***/ }),
1011
- /* 16 */
1043
+ /* 17 */
1012
1044
  /***/ (function(module, exports) {
1013
1045
 
1014
1046
  var g;
@@ -1034,7 +1066,7 @@ module.exports = g;
1034
1066
 
1035
1067
 
1036
1068
  /***/ }),
1037
- /* 17 */
1069
+ /* 18 */
1038
1070
  /***/ (function(module, exports, __webpack_require__) {
1039
1071
 
1040
1072
  "use strict";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-core",
3
- "version": "4.2.0",
3
+ "version": "4.3.1",
4
4
  "design": "v1",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -24,7 +24,7 @@
24
24
  "react-router-dom": "5.3.0"
25
25
  },
26
26
  "devDependencies": {
27
- "wb-dev-build-settings": "^0.2.0"
27
+ "wb-dev-build-settings": "^0.4.0"
28
28
  },
29
29
  "author": "",
30
30
  "license": "MIT"
@@ -532,8 +532,8 @@ exports[`wonder-blocks-core example 10 1`] = `
532
532
  }
533
533
  >
534
534
  <button
535
+ aria-disabled={false}
535
536
  className=""
536
- disabled={false}
537
537
  onBlur={[Function]}
538
538
  onClick={[Function]}
539
539
  onDragStart={[Function]}
@@ -0,0 +1,54 @@
1
+ // @flow
2
+ import * as React from "react";
3
+ import {render, act} from "@testing-library/react";
4
+ import {renderHook} from "@testing-library/react-hooks";
5
+
6
+ import {useForceUpdate} from "../use-force-update.js";
7
+
8
+ describe("#useForceUpdate", () => {
9
+ it("should return a function", () => {
10
+ // Arrange
11
+
12
+ // Act
13
+ const {
14
+ result: {current: result},
15
+ } = renderHook(() => useForceUpdate());
16
+
17
+ // Assert
18
+ expect(result).toBeInstanceOf(Function);
19
+ });
20
+
21
+ describe("returned function", () => {
22
+ beforeEach(() => {
23
+ jest.useFakeTimers();
24
+ });
25
+
26
+ it("should cause component to render", () => {
27
+ // Arrange
28
+ const Component = (props): React.Node => {
29
+ const countRef = React.useRef(0);
30
+ const forceUpdate = useForceUpdate();
31
+ React.useEffect(() => {
32
+ countRef.current++;
33
+
34
+ setTimeout(forceUpdate, 50);
35
+ });
36
+ return countRef.current;
37
+ };
38
+
39
+ // Act
40
+ const wrapper = render(<Component />);
41
+ act(() => {
42
+ // Advance enough for the timeout to run 4 times.
43
+ // Which means the component should have rendered 4 times,
44
+ // with one more pending for the timeout that was setup in
45
+ // the last render.
46
+ jest.advanceTimersByTime(204);
47
+ });
48
+ const result = wrapper.container.textContent;
49
+
50
+ // Assert
51
+ expect(result).toBe("4");
52
+ });
53
+ });
54
+ });
@@ -0,0 +1,23 @@
1
+ // @flow
2
+ import * as React from "react";
3
+
4
+ /**
5
+ * Hook for forcing a component to update on demand.
6
+ *
7
+ * This is for use inside other hooks that do some advanced
8
+ * trickery with storing state outside of React's own state
9
+ * mechanisms. As such this should never be called directly
10
+ * outside of a hook, and more often than not, is the wrong
11
+ * choice for whatever you are trying to do. If in doubt,
12
+ * don't use it.
13
+ *
14
+ * @returns {() => void} A function that forces the component to update.
15
+ */
16
+ export const useForceUpdate = (): (() => void) => {
17
+ const [, setState] = React.useState(false);
18
+ const forceUpdate = React.useCallback(
19
+ () => setState((state) => !state),
20
+ [],
21
+ );
22
+ return forceUpdate;
23
+ };
package/src/index.js CHANGED
@@ -12,6 +12,7 @@ export {
12
12
  useUniqueIdWithMock,
13
13
  useUniqueIdWithoutMock,
14
14
  } from "./hooks/use-unique-id.js";
15
+ export {useForceUpdate} from "./hooks/use-force-update.js";
15
16
  export {RenderStateRoot} from "./components/render-state-root.js";
16
17
 
17
18
  export type {AriaProps, IIdentifierFactory, StyleType};
@@ -45,7 +45,7 @@ StyleSheet as well inline style objects (see example 4).
45
45
 
46
46
  #### CSSProperties
47
47
 
48
- [See source file](https://github.com/Khan/wonder-blocks/blob/master/flow-typed/aphrodite.flow.js#L13)
48
+ [See source file](https://github.com/Khan/wonder-blocks/blob/main/flow-typed/aphrodite.flow.js#L13)
49
49
 
50
50
 
51
51
  ### Examples