@khanacademy/wonder-blocks-core 3.1.5 → 4.2.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/CHANGELOG.md +18 -0
- package/dist/es/index.js +157 -274
- package/dist/index.js +429 -394
- package/package.json +9 -10
- package/src/components/__tests__/id-provider.test.js +1 -0
- package/src/components/__tests__/render-state-root.test.js +91 -0
- package/src/components/__tests__/unique-id-provider.test.js +23 -0
- package/src/components/__tests__/with-ssr-placeholder.test.js +71 -0
- package/src/components/render-state-context.js +30 -0
- package/src/components/render-state-root.js +54 -0
- package/src/components/unique-id-provider.js +1 -0
- package/src/components/with-ssr-placeholder.js +41 -48
- package/src/hooks/__tests__/use-unique-id.test.js +257 -0
- package/src/hooks/use-unique-id.js +73 -0
- package/src/hooks/use-unique-id.md +16 -0
- package/src/hooks/use-unique-id.stories.mdx +158 -0
- package/src/index.js +6 -3
- package/src/util/__tests__/add-style.test.js +1 -0
- package/src/util/__tests__/enumerate-scroll-ancestors.test.js +1 -0
- package/src/util/add-style.js +2 -4
- package/LICENSE +0 -21
- package/src/util/__tests__/get-element-intersection.test.js +0 -425
- package/src/util/get-element-intersection.js +0 -132
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# @khanacademy/wonder-blocks-core
|
|
2
|
+
|
|
3
|
+
## 4.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 0aed8723: Adds `throwIfNested` required prop to `RenderStateRoot`.
|
|
8
|
+
|
|
9
|
+
## 4.1.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 45588e5f: Fix an issue with `useUniqueIdWithMock`/`useUniqueIdWithoutMock`
|
|
14
|
+
rerender more than was needed. The fix introduces `<RenderStateRoot>`
|
|
15
|
+
which must be an ancestor to all components uses these hooks.
|
|
16
|
+
- 875b7893: Nesting of `RenderStateRoot`s inside each other can result in extra renders
|
|
17
|
+
and potentially incorrect behavior. `RenderStateRoot` now throws if it
|
|
18
|
+
appears as a descendent of another `RenderStateRoot`.
|
package/dist/es/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import _extends from '@babel/runtime/helpers/extends';
|
|
2
2
|
import _objectWithoutPropertiesLoose from '@babel/runtime/helpers/objectWithoutPropertiesLoose';
|
|
3
|
-
import
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { useContext as useContext$1, useRef } from 'react';
|
|
4
5
|
import { StyleSheet, css } from 'aphrodite';
|
|
5
6
|
|
|
6
7
|
function flatten(list) {
|
|
@@ -76,9 +77,9 @@ function processStyleList(style) {
|
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
const _excluded = ["children", "style", "tag", "testId"];
|
|
80
|
+
const _excluded$2 = ["children", "style", "tag", "testId"];
|
|
80
81
|
const isHeaderRegex = /^h[1-6]$/;
|
|
81
|
-
const styles = StyleSheet.create({
|
|
82
|
+
const styles$1 = StyleSheet.create({
|
|
82
83
|
text: {
|
|
83
84
|
// Disable subpixel antialiasing on Mac desktop for consistency of
|
|
84
85
|
// rendering with mobile and Sketch (neither of which support it).
|
|
@@ -106,7 +107,7 @@ const styles = StyleSheet.create({
|
|
|
106
107
|
* - An array combining the above
|
|
107
108
|
*/
|
|
108
109
|
|
|
109
|
-
class Text extends Component {
|
|
110
|
+
class Text extends React.Component {
|
|
110
111
|
render() {
|
|
111
112
|
const _this$props = this.props,
|
|
112
113
|
{
|
|
@@ -115,11 +116,11 @@ class Text extends Component {
|
|
|
115
116
|
tag: Tag,
|
|
116
117
|
testId
|
|
117
118
|
} = _this$props,
|
|
118
|
-
otherProps = _objectWithoutPropertiesLoose(_this$props, _excluded);
|
|
119
|
+
otherProps = _objectWithoutPropertiesLoose(_this$props, _excluded$2);
|
|
119
120
|
|
|
120
121
|
const isHeader = isHeaderRegex.test(Tag);
|
|
121
|
-
const styleAttributes = processStyleList([styles.text, isHeader && styles.header, style]);
|
|
122
|
-
return /*#__PURE__*/createElement(Tag, _extends({}, otherProps, {
|
|
122
|
+
const styleAttributes = processStyleList([styles$1.text, isHeader && styles$1.header, style]);
|
|
123
|
+
return /*#__PURE__*/React.createElement(Tag, _extends({}, otherProps, {
|
|
123
124
|
style: styleAttributes.style,
|
|
124
125
|
className: styleAttributes.className,
|
|
125
126
|
"data-test-id": testId
|
|
@@ -149,7 +150,7 @@ function addStyle(Component, defaultStyle) {
|
|
|
149
150
|
className: aphroditeClassName,
|
|
150
151
|
style: inlineStyles
|
|
151
152
|
} = processStyleList([reset, defaultStyle, style]);
|
|
152
|
-
return /*#__PURE__*/createElement(Component, _extends({}, otherProps, {
|
|
153
|
+
return /*#__PURE__*/React.createElement(Component, _extends({}, otherProps, {
|
|
153
154
|
className: [aphroditeClassName, className].filter(Boolean).join(" "),
|
|
154
155
|
style: inlineStyles
|
|
155
156
|
}));
|
|
@@ -174,8 +175,8 @@ const overrides = StyleSheet.create({
|
|
|
174
175
|
}
|
|
175
176
|
});
|
|
176
177
|
|
|
177
|
-
const _excluded
|
|
178
|
-
const styles
|
|
178
|
+
const _excluded = ["testId", "tag"];
|
|
179
|
+
const styles = StyleSheet.create({
|
|
179
180
|
// https://github.com/facebook/css-layout#default-values
|
|
180
181
|
default: {
|
|
181
182
|
alignItems: "stretch",
|
|
@@ -193,11 +194,11 @@ const styles$1 = StyleSheet.create({
|
|
|
193
194
|
minWidth: 0
|
|
194
195
|
}
|
|
195
196
|
});
|
|
196
|
-
const StyledDiv = addStyle("div", styles
|
|
197
|
-
const StyledArticle = addStyle("article", styles
|
|
198
|
-
const StyledAside = addStyle("aside", styles
|
|
199
|
-
const StyledNav = addStyle("nav", styles
|
|
200
|
-
const StyledSection = addStyle("section", styles
|
|
197
|
+
const StyledDiv = addStyle("div", styles.default);
|
|
198
|
+
const StyledArticle = addStyle("article", styles.default);
|
|
199
|
+
const StyledAside = addStyle("aside", styles.default);
|
|
200
|
+
const StyledNav = addStyle("nav", styles.default);
|
|
201
|
+
const StyledSection = addStyle("section", styles.default);
|
|
201
202
|
/**
|
|
202
203
|
* View is a building block for constructing other components. `View` roughly
|
|
203
204
|
* maps to `div` and `Text` roughly maps to `span`. You can override which tag
|
|
@@ -212,14 +213,14 @@ const StyledSection = addStyle("section", styles$1.default);
|
|
|
212
213
|
* - An array combining the above
|
|
213
214
|
*/
|
|
214
215
|
|
|
215
|
-
class View extends Component {
|
|
216
|
+
class View extends React.Component {
|
|
216
217
|
render() {
|
|
217
218
|
const _this$props = this.props,
|
|
218
219
|
{
|
|
219
220
|
testId,
|
|
220
221
|
tag
|
|
221
222
|
} = _this$props,
|
|
222
|
-
restProps = _objectWithoutPropertiesLoose(_this$props, _excluded
|
|
223
|
+
restProps = _objectWithoutPropertiesLoose(_this$props, _excluded);
|
|
223
224
|
|
|
224
225
|
const props = _extends({}, restProps, {
|
|
225
226
|
"data-test-id": testId
|
|
@@ -227,19 +228,19 @@ class View extends Component {
|
|
|
227
228
|
|
|
228
229
|
switch (tag) {
|
|
229
230
|
case "article":
|
|
230
|
-
return /*#__PURE__*/createElement(StyledArticle, props);
|
|
231
|
+
return /*#__PURE__*/React.createElement(StyledArticle, props);
|
|
231
232
|
|
|
232
233
|
case "aside":
|
|
233
|
-
return /*#__PURE__*/createElement(StyledAside, props);
|
|
234
|
+
return /*#__PURE__*/React.createElement(StyledAside, props);
|
|
234
235
|
|
|
235
236
|
case "nav":
|
|
236
|
-
return /*#__PURE__*/createElement(StyledNav, props);
|
|
237
|
+
return /*#__PURE__*/React.createElement(StyledNav, props);
|
|
237
238
|
|
|
238
239
|
case "section":
|
|
239
|
-
return /*#__PURE__*/createElement(StyledSection, props);
|
|
240
|
+
return /*#__PURE__*/React.createElement(StyledSection, props);
|
|
240
241
|
|
|
241
242
|
case "div":
|
|
242
|
-
return /*#__PURE__*/createElement(StyledDiv, props);
|
|
243
|
+
return /*#__PURE__*/React.createElement(StyledDiv, props);
|
|
243
244
|
|
|
244
245
|
default:
|
|
245
246
|
throw Error(`${tag} is not an allowed value for the 'tag' prop`);
|
|
@@ -251,11 +252,11 @@ View.defaultProps = {
|
|
|
251
252
|
tag: "div"
|
|
252
253
|
};
|
|
253
254
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
255
|
+
const RenderState = require("flow-enums-runtime")({
|
|
256
|
+
Root: "root",
|
|
257
|
+
Initial: "initial",
|
|
258
|
+
Standard: "standard"
|
|
259
|
+
});
|
|
259
260
|
/**
|
|
260
261
|
* This is the context that tracks who is doing what in our SSR component tree.
|
|
261
262
|
*
|
|
@@ -271,7 +272,14 @@ View.defaultProps = {
|
|
|
271
272
|
* standard:
|
|
272
273
|
* means that we're all now doing non-SSR rendering
|
|
273
274
|
*/
|
|
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
|
+
|
|
275
283
|
/**
|
|
276
284
|
* Defer or change rendering until the component did mount.
|
|
277
285
|
*
|
|
@@ -296,8 +304,7 @@ const RenderStateContext = /*#__PURE__*/createContext("root");
|
|
|
296
304
|
* </WithSSRPlaceholder>
|
|
297
305
|
* ```
|
|
298
306
|
*/
|
|
299
|
-
|
|
300
|
-
class WithSSRPlaceholder extends Component {
|
|
307
|
+
class WithSSRPlaceholder extends React.Component {
|
|
301
308
|
constructor(...args) {
|
|
302
309
|
super(...args);
|
|
303
310
|
this.state = {
|
|
@@ -333,8 +340,8 @@ class WithSSRPlaceholder extends Component {
|
|
|
333
340
|
if (mounted) {
|
|
334
341
|
// This is our second non-SSR render, so let's tell everyone to
|
|
335
342
|
// do their thing.
|
|
336
|
-
return /*#__PURE__*/createElement(RenderStateContext.Provider, {
|
|
337
|
-
value:
|
|
343
|
+
return /*#__PURE__*/React.createElement(RenderStateContext.Provider, {
|
|
344
|
+
value: RenderState.Standard
|
|
338
345
|
}, children());
|
|
339
346
|
} // OK, this is the very first render.
|
|
340
347
|
// If we have a placeholder, we render it, and ensure that any
|
|
@@ -343,8 +350,8 @@ class WithSSRPlaceholder extends Component {
|
|
|
343
350
|
|
|
344
351
|
|
|
345
352
|
if (placeholder) {
|
|
346
|
-
return /*#__PURE__*/createElement(RenderStateContext.Provider, {
|
|
347
|
-
value:
|
|
353
|
+
return /*#__PURE__*/React.createElement(RenderStateContext.Provider, {
|
|
354
|
+
value: RenderState.Initial
|
|
348
355
|
}, placeholder());
|
|
349
356
|
} // Otherwise, we return nothing.
|
|
350
357
|
|
|
@@ -359,33 +366,10 @@ class WithSSRPlaceholder extends Component {
|
|
|
359
366
|
} = this.props;
|
|
360
367
|
|
|
361
368
|
switch (renderState) {
|
|
362
|
-
|
|
363
|
-
// context value here. So far it seems to be when we're nested in a
|
|
364
|
-
// v1 WithSSRPlaceholder equivalent component, or in some older
|
|
365
|
-
// React v16 situations where we're nested in the provider of a
|
|
366
|
-
// different context.
|
|
367
|
-
//
|
|
368
|
-
// We ignore this from coverage. It's a maintenance case to help
|
|
369
|
-
// us catch code changes that affect the control flow unexpectedly,
|
|
370
|
-
// but it's not something we need to write a test case for.
|
|
371
|
-
//
|
|
372
|
-
|
|
373
|
-
/* istanbul ignore next */
|
|
374
|
-
default:
|
|
375
|
-
// Let's log this case so we can debug it easily.
|
|
376
|
-
// Then fall through to the root case.
|
|
377
|
-
|
|
378
|
-
/* eslint-disable-next-line no-console */
|
|
379
|
-
console.log(`We got a render state we don't understand: "${JSON.stringify(renderState)}"`); // We "fallthrough" to the root case. This is more obvious
|
|
380
|
-
// and maintainable code than just ignoring the no-fallthrough
|
|
381
|
-
// lint rule.
|
|
382
|
-
|
|
383
|
-
return this._maybeRender("root");
|
|
384
|
-
|
|
385
|
-
case "root":
|
|
369
|
+
case RenderState.Root:
|
|
386
370
|
return this._renderAsRootComponent();
|
|
387
371
|
|
|
388
|
-
case
|
|
372
|
+
case RenderState.Initial:
|
|
389
373
|
// We're not the root component, so we just have to either
|
|
390
374
|
// render our placeholder or nothing.
|
|
391
375
|
// The second render is going to be triggered for us.
|
|
@@ -396,15 +380,42 @@ class WithSSRPlaceholder extends Component {
|
|
|
396
380
|
|
|
397
381
|
return null;
|
|
398
382
|
|
|
399
|
-
case
|
|
383
|
+
case RenderState.Standard:
|
|
400
384
|
// We have covered the SSR render, we're now rendering with
|
|
401
385
|
// standard rendering semantics.
|
|
402
386
|
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
|
+
|
|
403
|
+
|
|
404
|
+
{
|
|
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
|
+
|
|
413
|
+
return this._maybeRender(RenderState.Root);
|
|
403
414
|
}
|
|
404
415
|
}
|
|
405
416
|
|
|
406
417
|
render() {
|
|
407
|
-
return /*#__PURE__*/createElement(RenderStateContext.Consumer, null, value => this._maybeRender(value));
|
|
418
|
+
return /*#__PURE__*/React.createElement(RenderStateContext.Consumer, null, value => this._maybeRender(value));
|
|
408
419
|
}
|
|
409
420
|
|
|
410
421
|
}
|
|
@@ -510,7 +521,7 @@ var SsrIDFactory$1 = SsrIDFactory.Default;
|
|
|
510
521
|
* `get("test2")` will always equal `get("test2")`, but `get("test")` will
|
|
511
522
|
* never equal `get("test2")`.
|
|
512
523
|
*/
|
|
513
|
-
class UniqueIDProvider extends Component {
|
|
524
|
+
class UniqueIDProvider extends React.Component {
|
|
514
525
|
_performRender(firstRender) {
|
|
515
526
|
const {
|
|
516
527
|
children,
|
|
@@ -543,7 +554,7 @@ class UniqueIDProvider extends Component {
|
|
|
543
554
|
// Here we use the WithSSRPlaceholder component to control
|
|
544
555
|
// when we render and whether we provide a mock or real
|
|
545
556
|
// identifier factory.
|
|
546
|
-
return /*#__PURE__*/createElement(WithSSRPlaceholder, {
|
|
557
|
+
return /*#__PURE__*/React.createElement(WithSSRPlaceholder, {
|
|
547
558
|
placeholder: () => this._performRender(true)
|
|
548
559
|
}, () => this._performRender(false));
|
|
549
560
|
}
|
|
@@ -564,7 +575,7 @@ class UniqueIDProvider extends Component {
|
|
|
564
575
|
* e.g. It uses the same generated id to connect a Dialog with its main title, or form label
|
|
565
576
|
* with the associated input element, etc.
|
|
566
577
|
*/
|
|
567
|
-
class IDProvider extends Component {
|
|
578
|
+
class IDProvider extends React.Component {
|
|
568
579
|
renderChildren(ids) {
|
|
569
580
|
const {
|
|
570
581
|
id,
|
|
@@ -590,7 +601,7 @@ class IDProvider extends Component {
|
|
|
590
601
|
// need it.
|
|
591
602
|
return this.renderChildren();
|
|
592
603
|
} else {
|
|
593
|
-
return /*#__PURE__*/createElement(UniqueIDProvider, {
|
|
604
|
+
return /*#__PURE__*/React.createElement(UniqueIDProvider, {
|
|
594
605
|
scope: scope,
|
|
595
606
|
mockOnFirstRender: true
|
|
596
607
|
}, ids => this.renderChildren(ids));
|
|
@@ -600,237 +611,109 @@ class IDProvider extends Component {
|
|
|
600
611
|
}
|
|
601
612
|
IDProvider.defaultId = "wb-id";
|
|
602
613
|
|
|
603
|
-
let
|
|
614
|
+
let serverSide = false;
|
|
615
|
+
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
|
+
isServerSide: () => serverSide,
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Set server-side mode to true.
|
|
626
|
+
*/
|
|
627
|
+
setServerSide: () => {
|
|
628
|
+
serverSide = true;
|
|
629
|
+
}
|
|
630
|
+
};
|
|
604
631
|
|
|
605
632
|
/**
|
|
606
|
-
*
|
|
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.
|
|
607
636
|
*
|
|
608
|
-
*
|
|
609
|
-
*
|
|
610
|
-
* ancestors.
|
|
611
|
-
*
|
|
612
|
-
* Also modified for our standards (and commented a bit).
|
|
637
|
+
* @param {string} [scope] optional string to prefix generated ids with.
|
|
638
|
+
* @returns {IIdentifierFactory}
|
|
613
639
|
*/
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
640
|
+
const useUniqueIdWithMock = scope => {
|
|
641
|
+
const renderState = useContext$1(RenderStateContext);
|
|
642
|
+
const idFactory = useRef(null);
|
|
617
643
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
};
|
|
621
|
-
|
|
622
|
-
const canScroll = function canScroll(node) {
|
|
623
|
-
return /(auto|scroll)/.test(getElementOverflow(node));
|
|
624
|
-
}; // NOTE(somewhatabstract): Flow includes the @@iterator value in the Iterator
|
|
625
|
-
// interface definition, but it doesn't pick up on our computed property
|
|
626
|
-
// defining it (tried handling this in other ways, but it just won't work).
|
|
627
|
-
// So, we need this and the additional FlowFixMe a few lines down on the
|
|
628
|
-
// iterator property itself.
|
|
629
|
-
// $FlowFixMe[prop-missing]
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
_Symbol$iterator = Symbol.iterator;
|
|
633
|
-
|
|
634
|
-
class ScrollAncestorsIterator {
|
|
635
|
-
constructor(element) {
|
|
636
|
-
this.done = false;
|
|
637
|
-
|
|
638
|
-
if (!(element instanceof HTMLElement)) {
|
|
639
|
-
this.done = true;
|
|
640
|
-
} else {
|
|
641
|
-
this.parentElement = element.parentElement;
|
|
642
|
-
}
|
|
643
|
-
} //$FlowFixMe: Computed property keys are not allowed by flow at the moment
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
[_Symbol$iterator]() {
|
|
647
|
-
return this;
|
|
644
|
+
if (renderState === RenderState.Root) {
|
|
645
|
+
throw new Error("Components using useUniqueIdWithMock() should be descendants of <RenderStateRoot>");
|
|
648
646
|
}
|
|
649
647
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
return {
|
|
653
|
-
done: true
|
|
654
|
-
};
|
|
655
|
-
} // Climb the DOM looking for the next scroll candidate.
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
let scrollCandidate;
|
|
659
|
-
|
|
660
|
-
do {
|
|
661
|
-
scrollCandidate = this.parentElement;
|
|
662
|
-
this.parentElement = this.parentElement && this.parentElement.parentElement;
|
|
663
|
-
} while (scrollCandidate && !canScroll(scrollCandidate));
|
|
664
|
-
|
|
665
|
-
if (!scrollCandidate) {
|
|
666
|
-
// If we don't have a scroll candidate, we'll definitely be done
|
|
667
|
-
// iterating by the next call to next().
|
|
668
|
-
// So let's remember that.
|
|
669
|
-
this.done = true; // If we don't have a documentElement, we are actually done right
|
|
670
|
-
// now, rather than on the next call.
|
|
671
|
-
|
|
672
|
-
if (!document.documentElement) {
|
|
673
|
-
return {
|
|
674
|
-
done: true
|
|
675
|
-
};
|
|
676
|
-
} // Otherwise, as we have a documentElement, this is our penultimate
|
|
677
|
-
// iteration .
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
return {
|
|
681
|
-
done: false,
|
|
682
|
-
value: document.documentElement
|
|
683
|
-
};
|
|
684
|
-
} // We found a scroll ancestor, so let's return that.
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
return {
|
|
688
|
-
done: false,
|
|
689
|
-
value: scrollCandidate
|
|
690
|
-
};
|
|
648
|
+
if (renderState === RenderState.Initial) {
|
|
649
|
+
return SsrIDFactory$1;
|
|
691
650
|
}
|
|
692
651
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
function enumerateScrollAncestors(element) {
|
|
696
|
-
return Object.defineProperty({}, Symbol.iterator, {
|
|
697
|
-
value: () => new ScrollAncestorsIterator(element),
|
|
698
|
-
writable: true
|
|
699
|
-
});
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
function getAxisIntersection(intersectingRect, boundsRect, axis) {
|
|
703
|
-
const start = rect => axis === "horizontal" ? rect.left : rect.top;
|
|
704
|
-
|
|
705
|
-
const end = rect => axis === "horizontal" ? rect.right : rect.bottom;
|
|
706
|
-
|
|
707
|
-
if (end(intersectingRect) <= start(boundsRect)) {
|
|
708
|
-
return "before";
|
|
709
|
-
} else if (start(intersectingRect) >= end(boundsRect)) {
|
|
710
|
-
return "after";
|
|
652
|
+
if (!idFactory.current) {
|
|
653
|
+
idFactory.current = new UniqueIDFactory(scope);
|
|
711
654
|
}
|
|
712
655
|
|
|
713
|
-
return
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
function UndeterminedIntersection() {
|
|
717
|
-
return {
|
|
718
|
-
horizontal: null,
|
|
719
|
-
vertical: null
|
|
720
|
-
};
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
function FullIntersection() {
|
|
724
|
-
return {
|
|
725
|
-
horizontal: "within",
|
|
726
|
-
vertical: "within"
|
|
727
|
-
};
|
|
728
|
-
}
|
|
656
|
+
return idFactory.current;
|
|
657
|
+
};
|
|
729
658
|
/**
|
|
730
|
-
*
|
|
659
|
+
* Returns a unique identifier factory. If the parent component hasn't
|
|
660
|
+
* been mounted yet, null will be returned.
|
|
731
661
|
*
|
|
732
|
-
*
|
|
733
|
-
*
|
|
662
|
+
* @param {string} [scope] optional string to prefix generated ids with.
|
|
663
|
+
* @returns {?IIdentifierFactory}
|
|
734
664
|
*/
|
|
735
665
|
|
|
666
|
+
const useUniqueIdWithoutMock = scope => {
|
|
667
|
+
const renderState = useContext$1(RenderStateContext);
|
|
668
|
+
const idFactory = useRef(null);
|
|
736
669
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
// element isn't clipping or otherwise hiding things outside its bounds,
|
|
740
|
-
// then checking against bounds isn't going to be much use.
|
|
741
|
-
// So, let's get the style for the element and use the overflow values.
|
|
742
|
-
const style = boundsElement.currentStyle || window.getComputedStyle(boundsElement);
|
|
743
|
-
const boundingRect = boundsElement.getBoundingClientRect();
|
|
744
|
-
const boundsRect = {
|
|
745
|
-
top: boundingRect.top,
|
|
746
|
-
bottom: boundingRect.bottom,
|
|
747
|
-
left: boundingRect.left,
|
|
748
|
-
right: boundingRect.right
|
|
749
|
-
}; // In webapp we set height: 100% on html, body and overflow-y: scroll on body.
|
|
750
|
-
// This results in the height reported by getBoundingClientRect being the height
|
|
751
|
-
// of the viewport instead of the height of the page. We use the scrollHeight
|
|
752
|
-
// of the body to corect the bounds.
|
|
753
|
-
// TODO(kevinb): screenshot test this
|
|
754
|
-
|
|
755
|
-
if (boundsElement === document.body) {
|
|
756
|
-
boundsRect.bottom = boundsRect.top + boundsElement.scrollHeight;
|
|
757
|
-
} // We assume we're within this specific bounds element if it's overflow is
|
|
758
|
-
// visible.
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
const horizontal = style.overflowX === "visible" ? "within" : getAxisIntersection(intersectingRect, boundsRect, "horizontal"); // We assume we're within this specific bounds element if it's overflow is
|
|
762
|
-
// visible.
|
|
763
|
-
|
|
764
|
-
const vertical = style.overflowY === "visible" ? "within" : getAxisIntersection(intersectingRect, boundsRect, "vertical");
|
|
765
|
-
return {
|
|
766
|
-
horizontal,
|
|
767
|
-
vertical
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
/**
|
|
771
|
-
* Determine if a given element intersects with the visible bounds of its
|
|
772
|
-
* scroll parents, or the bounds of a specific element.
|
|
773
|
-
*/
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
function getElementIntersection(element, boundsElement) {
|
|
777
|
-
if (!element) {
|
|
778
|
-
// An non-existant element is definitely not visible.
|
|
779
|
-
return UndeterminedIntersection();
|
|
670
|
+
if (renderState === RenderState.Root) {
|
|
671
|
+
throw new Error("Components using useUniqueIdWithoutMock() should be descendants of <RenderStateRoot>");
|
|
780
672
|
}
|
|
781
673
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
left,
|
|
785
|
-
bottom,
|
|
786
|
-
right
|
|
787
|
-
} = element.getBoundingClientRect();
|
|
788
|
-
const intersectingRect = {
|
|
789
|
-
top,
|
|
790
|
-
left,
|
|
791
|
-
bottom,
|
|
792
|
-
right
|
|
793
|
-
}; // If we're looking against a single boundary element, then we just do that.
|
|
794
|
-
|
|
795
|
-
if (boundsElement) {
|
|
796
|
-
return getElementIntersectionAgainstParent(intersectingRect, boundsElement);
|
|
674
|
+
if (renderState === RenderState.Initial) {
|
|
675
|
+
return null;
|
|
797
676
|
}
|
|
798
677
|
|
|
799
|
-
if (
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
for (const scrollParent of enumerateScrollAncestors(element)) {
|
|
803
|
-
const intersection = getElementIntersectionAgainstParent(intersectingRect, scrollParent); // If the intersectingRect is before or after the parent in one or both
|
|
804
|
-
// dimensions, then return our intersection result. Otherwise, we'll
|
|
805
|
-
// keep on searching up our parents.
|
|
806
|
-
|
|
807
|
-
if (intersection.vertical !== "within" || intersection.horizontal !== "within") {
|
|
808
|
-
// Stop looking, we've found something that is hiding the element.
|
|
809
|
-
return intersection;
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
} // If we got here, the element is within the bounds of its parents.
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
return FullIntersection();
|
|
816
|
-
}
|
|
678
|
+
if (!idFactory.current) {
|
|
679
|
+
idFactory.current = new UniqueIDFactory(scope);
|
|
680
|
+
}
|
|
817
681
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
/**
|
|
821
|
-
* Check if we are running in server-side mode.
|
|
822
|
-
*
|
|
823
|
-
* @returns {boolean} `true` if we are in server-side mode; otherwise,
|
|
824
|
-
* `false`
|
|
825
|
-
*/
|
|
826
|
-
isServerSide: () => serverSide,
|
|
682
|
+
return idFactory.current;
|
|
683
|
+
};
|
|
827
684
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
685
|
+
const {
|
|
686
|
+
useContext,
|
|
687
|
+
useEffect,
|
|
688
|
+
useState
|
|
689
|
+
} = React;
|
|
690
|
+
const RenderStateRoot = ({
|
|
691
|
+
children,
|
|
692
|
+
throwIfNested
|
|
693
|
+
}) => {
|
|
694
|
+
const [firstRender, setFirstRender] = useState(true);
|
|
695
|
+
const contextValue = useContext(RenderStateContext);
|
|
696
|
+
useEffect(() => {
|
|
697
|
+
setFirstRender(false);
|
|
698
|
+
}, []); // This effect will only run once.
|
|
699
|
+
|
|
700
|
+
if (contextValue !== RenderState.Root) {
|
|
701
|
+
if (throwIfNested) {
|
|
702
|
+
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
|
+
|
|
706
|
+
|
|
707
|
+
return children;
|
|
833
708
|
}
|
|
709
|
+
|
|
710
|
+
const value = firstRender ? RenderState.Initial : RenderState.Standard;
|
|
711
|
+
return /*#__PURE__*/React.createElement(RenderStateContext.Provider, {
|
|
712
|
+
value: value
|
|
713
|
+
}, children);
|
|
714
|
+
};
|
|
715
|
+
RenderStateRoot.defaultProps = {
|
|
716
|
+
throwIfNested: true
|
|
834
717
|
};
|
|
835
718
|
|
|
836
|
-
export { IDProvider, server as Server, Text, UniqueIDProvider, View, WithSSRPlaceholder, addStyle,
|
|
719
|
+
export { IDProvider, RenderStateRoot, server as Server, Text, UniqueIDProvider, View, WithSSRPlaceholder, addStyle, useUniqueIdWithMock, useUniqueIdWithoutMock };
|