Grabbed dndbeyond's source code

```
~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js
```
This commit is contained in:
2025-05-28 11:50:03 -07:00
commit 8df9031d27
3592 changed files with 319051 additions and 0 deletions
@@ -0,0 +1,50 @@
var unitlessKeys = {
animationIterationCount: 1,
borderImageOutset: 1,
borderImageSlice: 1,
borderImageWidth: 1,
boxFlex: 1,
boxFlexGroup: 1,
boxOrdinalGroup: 1,
columnCount: 1,
columns: 1,
flex: 1,
flexGrow: 1,
flexPositive: 1,
flexShrink: 1,
flexNegative: 1,
flexOrder: 1,
gridRow: 1,
gridRowEnd: 1,
gridRowSpan: 1,
gridRowStart: 1,
gridColumn: 1,
gridColumnEnd: 1,
gridColumnSpan: 1,
gridColumnStart: 1,
msGridRow: 1,
msGridRowSpan: 1,
msGridColumn: 1,
msGridColumnSpan: 1,
fontWeight: 1,
lineHeight: 1,
opacity: 1,
order: 1,
orphans: 1,
tabSize: 1,
widows: 1,
zIndex: 1,
zoom: 1,
WebkitLineClamp: 1,
// SVG-related properties
fillOpacity: 1,
floodOpacity: 1,
stopOpacity: 1,
strokeDasharray: 1,
strokeDashoffset: 1,
strokeMiterlimit: 1,
strokeOpacity: 1,
strokeWidth: 1
};
export default unitlessKeys;
+74
View File
@@ -0,0 +1,74 @@
// @flow
/* Import singletons */
import isStyledComponent from './utils/isStyledComponent';
import css from './constructors/css';
import createGlobalStyle from './constructors/createGlobalStyle';
import keyframes from './constructors/keyframes';
import ServerStyleSheet from './models/ServerStyleSheet';
import { SC_VERSION } from './constants';
import StyleSheetManager, {
StyleSheetContext,
StyleSheetConsumer,
} from './models/StyleSheetManager';
/* Import components */
import ThemeProvider, { ThemeContext, ThemeConsumer } from './models/ThemeProvider';
/* Import Higher Order Components */
import withTheme from './hoc/withTheme';
/* Import hooks */
import useTheme from './hooks/useTheme';
declare var __SERVER__: boolean;
/* Warning if you've imported this file on React Native */
if (
process.env.NODE_ENV !== 'production' &&
typeof navigator !== 'undefined' &&
navigator.product === 'ReactNative'
) {
// eslint-disable-next-line no-console
console.warn(
"It looks like you've imported 'styled-components' on React Native.\n" +
"Perhaps you're looking to import 'styled-components/native'?\n" +
'Read more about this at https://www.styled-components.com/docs/basics#react-native'
);
}
/* Warning if there are several instances of styled-components */
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test' && typeof window !== 'undefined') {
window['__styled-components-init__'] = window['__styled-components-init__'] || 0;
if (window['__styled-components-init__'] === 1) {
// eslint-disable-next-line no-console
console.warn(
"It looks like there are several instances of 'styled-components' initialized in this application. " +
'This may cause dynamic styles to not render properly, errors during the rehydration process, ' +
'a missing theme prop, and makes your application bigger without good reason.\n\n' +
'See https://s-c.sh/2BAXzed for more info.'
);
}
window['__styled-components-init__'] += 1;
}
/* Export everything */
export * from './secretInternals';
export {
createGlobalStyle,
css,
isStyledComponent,
keyframes,
ServerStyleSheet,
StyleSheetConsumer,
StyleSheetContext,
StyleSheetManager,
ThemeConsumer,
ThemeContext,
ThemeProvider,
useTheme,
SC_VERSION as version,
withTheme,
};
+37
View File
@@ -0,0 +1,37 @@
// @flow
declare var SC_DISABLE_SPEEDY: ?boolean;
declare var __VERSION__: string;
export const SC_ATTR: string =
(typeof process !== 'undefined' &&
typeof process.env !== 'undefined' &&
(process.env.REACT_APP_SC_ATTR || process.env.SC_ATTR)) ||
'data-styled';
export const SC_ATTR_ACTIVE = 'active';
export const SC_ATTR_VERSION = 'data-styled-version';
export const SC_VERSION = __VERSION__;
export const SPLITTER = '/*!sc*/\n';
export const IS_BROWSER = typeof window !== 'undefined' && 'HTMLElement' in window;
export const DISABLE_SPEEDY = Boolean(
typeof SC_DISABLE_SPEEDY === 'boolean'
? SC_DISABLE_SPEEDY
: typeof process !== 'undefined' && typeof process.env !== 'undefined'
? typeof process.env.REACT_APP_SC_DISABLE_SPEEDY !== 'undefined' &&
process.env.REACT_APP_SC_DISABLE_SPEEDY !== ''
? process.env.REACT_APP_SC_DISABLE_SPEEDY === 'false'
? false
: process.env.REACT_APP_SC_DISABLE_SPEEDY
: typeof process.env.SC_DISABLE_SPEEDY !== 'undefined' && process.env.SC_DISABLE_SPEEDY !== ''
? process.env.SC_DISABLE_SPEEDY === 'false'
? false
: process.env.SC_DISABLE_SPEEDY
: process.env.NODE_ENV !== 'production'
: false
);
// Shared empty execution context when generating static styles
export const STATIC_EXECUTION_CONTEXT = {};
@@ -0,0 +1,34 @@
// @flow
import { isValidElementType } from 'react-is';
import css from './css';
import throwStyledError from '../utils/error';
import { EMPTY_OBJECT } from '../utils/empties';
import type { Target } from '../types';
export default function constructWithOptions(
componentConstructor: Function,
tag: Target,
options: Object = EMPTY_OBJECT
) {
if (!isValidElementType(tag)) {
return throwStyledError(1, String(tag));
}
/* This is callable directly as a template function */
// $FlowFixMe: Not typed to avoid destructuring arguments
const templateFunction = (...args) => componentConstructor(tag, options, css(...args));
/* If config methods are called, wrap up a new template function and merge options */
templateFunction.withConfig = config =>
constructWithOptions(componentConstructor, tag, { ...options, ...config });
/* Modify/inject new props at runtime */
templateFunction.attrs = attrs =>
constructWithOptions(componentConstructor, tag, {
...options,
attrs: Array.prototype.concat(options.attrs, attrs).filter(Boolean),
});
return templateFunction;
}
+34
View File
@@ -0,0 +1,34 @@
// @flow
import interleave from '../utils/interleave';
import isPlainObject from '../utils/isPlainObject';
import { EMPTY_ARRAY } from '../utils/empties';
import isFunction from '../utils/isFunction';
import flatten from '../utils/flatten';
import type { Interpolation, RuleSet, Styles } from '../types';
/**
* Used when flattening object styles to determine if we should
* expand an array of styles.
*/
const addTag = arg => {
if (Array.isArray(arg)) {
// eslint-disable-next-line no-param-reassign
arg.isCss = true;
}
return arg;
};
export default function css(styles: Styles, ...interpolations: Array<Interpolation>): RuleSet {
if (isFunction(styles) || isPlainObject(styles)) {
// $FlowFixMe
return addTag(flatten(interleave(EMPTY_ARRAY, [styles, ...interpolations])));
}
if (interpolations.length === 0 && styles.length === 1 && typeof styles[0] === 'string') {
// $FlowFixMe
return styles;
}
// $FlowFixMe
return addTag(flatten(interleave(styles, interpolations)));
}
+15
View File
@@ -0,0 +1,15 @@
// @flow
import constructWithOptions from './constructWithOptions';
import StyledComponent from '../models/StyledComponent';
import domElements from '../utils/domElements';
import type { Target } from '../types';
const styled = (tag: Target) => constructWithOptions(StyledComponent, tag);
// Shorthands for all valid HTML Elements
domElements.forEach(domElement => {
styled[domElement] = styled(domElement);
});
export default styled;
+112
View File
@@ -0,0 +1,112 @@
// @flow
import { SC_VERSION } from '../constants';
import StyleSheet from '../sheet';
import type { RuleSet, Stringifier } from '../types';
import flatten from '../utils/flatten';
import generateName from '../utils/generateAlphabeticName';
import { hash, phash } from '../utils/hash';
import isStaticRules from '../utils/isStaticRules';
const SEED = hash(SC_VERSION);
/**
* ComponentStyle is all the CSS-specific stuff, not the React-specific stuff.
*/
export default class ComponentStyle {
baseHash: number;
baseStyle: ?ComponentStyle;
componentId: string;
isStatic: boolean;
rules: RuleSet;
staticRulesId: string;
constructor(rules: RuleSet, componentId: string, baseStyle?: ComponentStyle) {
this.rules = rules;
this.staticRulesId = '';
this.isStatic = process.env.NODE_ENV === 'production' &&
(baseStyle === undefined || baseStyle.isStatic) &&
isStaticRules(rules);
this.componentId = componentId;
// SC_VERSION gives us isolation between multiple runtimes on the page at once
// this is improved further with use of the babel plugin "namespace" feature
this.baseHash = phash(SEED, componentId);
this.baseStyle = baseStyle;
// NOTE: This registers the componentId, which ensures a consistent order
// for this component's styles compared to others
StyleSheet.registerId(componentId);
}
/*
* Flattens a rule set into valid CSS
* Hashes it, wraps the whole chunk in a .hash1234 {}
* Returns the hash to be injected on render()
* */
generateAndInjectStyles(executionContext: Object, styleSheet: StyleSheet, stylis: Stringifier) {
const { componentId } = this;
const names = [];
if (this.baseStyle) {
names.push(this.baseStyle.generateAndInjectStyles(executionContext, styleSheet, stylis));
}
// force dynamic classnames if user-supplied stylis plugins are in use
if (this.isStatic && !stylis.hash) {
if (this.staticRulesId && styleSheet.hasNameForId(componentId, this.staticRulesId)) {
names.push(this.staticRulesId);
} else {
const cssStatic = flatten(this.rules, executionContext, styleSheet, stylis).join('');
const name = generateName(phash(this.baseHash, cssStatic) >>> 0);
if (!styleSheet.hasNameForId(componentId, name)) {
const cssStaticFormatted = stylis(cssStatic, `.${name}`, undefined, componentId);
styleSheet.insertRules(componentId, name, cssStaticFormatted);
}
names.push(name);
this.staticRulesId = name;
}
} else {
const { length } = this.rules;
let dynamicHash = phash(this.baseHash, stylis.hash);
let css = '';
for (let i = 0; i < length; i++) {
const partRule = this.rules[i];
if (typeof partRule === 'string') {
css += partRule;
if (process.env.NODE_ENV !== 'production') dynamicHash = phash(dynamicHash, partRule + i);
} else if (partRule) {
const partChunk = flatten(partRule, executionContext, styleSheet, stylis);
const partString = Array.isArray(partChunk) ? partChunk.join('') : partChunk;
dynamicHash = phash(dynamicHash, partString + i);
css += partString;
}
}
if (css) {
const name = generateName(dynamicHash >>> 0);
if (!styleSheet.hasNameForId(componentId, name)) {
const cssFormatted = stylis(css, `.${name}`, undefined, componentId);
styleSheet.insertRules(componentId, name, cssFormatted);
}
names.push(name);
}
}
return names.join(' ');
}
}
+54
View File
@@ -0,0 +1,54 @@
// @flow
import StyleSheet from '../sheet';
import type { RuleSet, Stringifier } from '../types';
import flatten from '../utils/flatten';
import isStaticRules from '../utils/isStaticRules';
export default class GlobalStyle {
componentId: string;
isStatic: boolean;
rules: RuleSet;
constructor(rules: RuleSet, componentId: string) {
this.rules = rules;
this.componentId = componentId;
this.isStatic = isStaticRules(rules);
// pre-register the first instance to ensure global styles
// load before component ones
StyleSheet.registerId(this.componentId + 1);
}
createStyles(
instance: number,
executionContext: Object,
styleSheet: StyleSheet,
stylis: Stringifier
) {
const flatCSS = flatten(this.rules, executionContext, styleSheet, stylis);
const css = stylis(flatCSS.join(''), '');
const id = this.componentId + instance;
// NOTE: We use the id as a name as well, since these rules never change
styleSheet.insertRules(id, id, css);
}
removeStyles(instance: number, styleSheet: StyleSheet) {
styleSheet.clearRules(this.componentId + instance);
}
renderStyles(
instance: number,
executionContext: Object,
styleSheet: StyleSheet,
stylis: Stringifier
) {
if (instance > 2) StyleSheet.registerId(this.componentId + instance);
// NOTE: Remove old styles, then inject the new ones
this.removeStyles(instance, styleSheet);
this.createStyles(instance, executionContext, styleSheet, stylis);
}
}
+39
View File
@@ -0,0 +1,39 @@
// @flow
import StyleSheet from '../sheet';
import { type Stringifier } from '../types';
import throwStyledError from '../utils/error';
import { masterStylis } from './StyleSheetManager';
export default class Keyframes {
id: string;
name: string;
rules: string;
constructor(name: string, rules: string) {
this.name = name;
this.id = `sc-keyframes-${name}`;
this.rules = rules;
}
inject = (styleSheet: StyleSheet, stylisInstance: Stringifier = masterStylis) => {
const resolvedName = this.name + stylisInstance.hash;
if (!styleSheet.hasNameForId(this.id, resolvedName)) {
styleSheet.insertRules(
this.id,
resolvedName,
stylisInstance(this.rules, resolvedName, '@keyframes')
);
}
};
toString = () => {
return throwStyledError(12, String(this.name));
};
getName(stylisInstance: Stringifier = masterStylis) {
return this.name + stylisInstance.hash;
}
}
+129
View File
@@ -0,0 +1,129 @@
// @flow
/* eslint-disable no-underscore-dangle */
import React from 'react';
import { IS_BROWSER, SC_ATTR, SC_ATTR_VERSION, SC_VERSION } from '../constants';
import throwStyledError from '../utils/error';
import getNonce from '../utils/nonce';
import StyleSheet from '../sheet';
import StyleSheetManager from './StyleSheetManager';
declare var __SERVER__: boolean;
const CLOSING_TAG_R = /^\s*<\/[a-z]/i;
export default class ServerStyleSheet {
isStreaming: boolean;
instance: StyleSheet;
sealed: boolean;
constructor() {
this.instance = new StyleSheet({ isServer: true });
this.sealed = false;
}
_emitSheetCSS = (): string => {
const css = this.instance.toString();
if (!css) return '';
const nonce = getNonce();
const attrs = [nonce && `nonce="${nonce}"`, `${SC_ATTR}="true"`, `${SC_ATTR_VERSION}="${SC_VERSION}"`];
const htmlAttr = attrs.filter(Boolean).join(' ');
return `<style ${htmlAttr}>${css}</style>`;
};
collectStyles(children: any) {
if (this.sealed) {
return throwStyledError(2);
}
return <StyleSheetManager sheet={this.instance}>{children}</StyleSheetManager>;
}
getStyleTags = (): string => {
if (this.sealed) {
return throwStyledError(2);
}
return this._emitSheetCSS();
};
getStyleElement = () => {
if (this.sealed) {
return throwStyledError(2);
}
const props = {
[SC_ATTR]: '',
[SC_ATTR_VERSION]: SC_VERSION,
dangerouslySetInnerHTML: {
__html: this.instance.toString(),
},
};
const nonce = getNonce();
if (nonce) {
(props: any).nonce = nonce;
}
// v4 returned an array for this fn, so we'll do the same for v5 for backward compat
return [<style {...props} key="sc-0-0" />];
};
// eslint-disable-next-line consistent-return
interleaveWithNodeStream(input: any) {
if (!__SERVER__ || IS_BROWSER) {
return throwStyledError(3);
} else if (this.sealed) {
return throwStyledError(2);
}
if (__SERVER__) {
this.seal();
// eslint-disable-next-line global-require
const { Readable, Transform } = require('stream');
const readableStream: Readable = input;
const { instance: sheet, _emitSheetCSS } = this;
const transformer = new Transform({
transform: function appendStyleChunks(chunk, /* encoding */ _, callback) {
// Get the chunk and retrieve the sheet's CSS as an HTML chunk,
// then reset its rules so we get only new ones for the next chunk
const renderedHtml = chunk.toString();
const html = _emitSheetCSS();
sheet.clearTag();
// prepend style html to chunk, unless the start of the chunk is a
// closing tag in which case append right after that
if (CLOSING_TAG_R.test(renderedHtml)) {
const endOfClosingTag = renderedHtml.indexOf('>') + 1;
const before = renderedHtml.slice(0, endOfClosingTag);
const after = renderedHtml.slice(endOfClosingTag);
this.push(before + html + after);
} else {
this.push(html + renderedHtml);
}
callback();
},
});
readableStream.on('error', err => {
// forward the error to the transform stream
transformer.emit('error', err);
});
return readableStream.pipe(transformer);
}
}
seal = () => {
this.sealed = true;
};
}
@@ -0,0 +1,76 @@
// @flow
import React, { type Context, type Node, useContext, useEffect, useMemo, useState } from 'react';
import shallowequal from 'shallowequal';
import StyleSheet from '../sheet';
import type { Stringifier } from '../types';
import createStylisInstance from '../utils/stylis';
type Props = {
children?: Node,
disableCSSOMInjection?: boolean,
disableVendorPrefixes?: boolean,
sheet?: StyleSheet,
stylisPlugins?: Array<Function>,
target?: HTMLElement,
};
export const StyleSheetContext: Context<StyleSheet | void> = React.createContext();
export const StyleSheetConsumer = StyleSheetContext.Consumer;
export const StylisContext: Context<Stringifier | void> = React.createContext();
export const StylisConsumer = StylisContext.Consumer;
export const masterSheet: StyleSheet = new StyleSheet();
export const masterStylis: Stringifier = createStylisInstance();
export function useStyleSheet(): StyleSheet {
return useContext(StyleSheetContext) || masterSheet;
}
export function useStylis(): Stringifier {
return useContext(StylisContext) || masterStylis;
}
export default function StyleSheetManager(props: Props) {
const [plugins, setPlugins] = useState(props.stylisPlugins);
const contextStyleSheet = useStyleSheet();
const styleSheet = useMemo(() => {
let sheet = contextStyleSheet;
if (props.sheet) {
// eslint-disable-next-line prefer-destructuring
sheet = props.sheet;
} else if (props.target) {
sheet = sheet.reconstructWithOptions({ target: props.target }, false);
}
if (props.disableCSSOMInjection) {
sheet = sheet.reconstructWithOptions({ useCSSOMInjection: false });
}
return sheet;
}, [props.disableCSSOMInjection, props.sheet, props.target]);
const stylis = useMemo(
() =>
createStylisInstance({
options: { prefix: !props.disableVendorPrefixes },
plugins,
}),
[props.disableVendorPrefixes, plugins]
);
useEffect(() => {
if (!shallowequal(plugins, props.stylisPlugins)) setPlugins(props.stylisPlugins);
}, [props.stylisPlugins]);
return (
<StyleSheetContext.Provider value={styleSheet}>
<StylisContext.Provider value={stylis}>
{process.env.NODE_ENV !== 'production'
? React.Children.only(props.children)
: props.children}
</StylisContext.Provider>
</StyleSheetContext.Provider>
);
}
+327
View File
@@ -0,0 +1,327 @@
// @flow
import validAttr from '@emotion/is-prop-valid';
import hoist from 'hoist-non-react-statics';
import React, { createElement, type Ref, useContext } from 'react';
import { SC_VERSION } from '../constants';
import type {
Attrs,
IStyledComponent,
IStyledStatics,
RuleSet,
ShouldForwardProp,
Target,
} from '../types';
import { checkDynamicCreation } from '../utils/checkDynamicCreation';
import createWarnTooManyClasses from '../utils/createWarnTooManyClasses';
import determineTheme from '../utils/determineTheme';
import { EMPTY_ARRAY, EMPTY_OBJECT } from '../utils/empties';
import escape from '../utils/escape';
import generateComponentId from '../utils/generateComponentId';
import generateDisplayName from '../utils/generateDisplayName';
import getComponentName from '../utils/getComponentName';
import isFunction from '../utils/isFunction';
import isStyledComponent from '../utils/isStyledComponent';
import isTag from '../utils/isTag';
import joinStrings from '../utils/joinStrings';
import merge from '../utils/mixinDeep';
import ComponentStyle from './ComponentStyle';
import { useStyleSheet, useStylis } from './StyleSheetManager';
import { ThemeContext } from './ThemeProvider';
const identifiers = {};
/* We depend on components having unique IDs */
function generateId(displayName?: string, parentComponentId?: string) {
const name = typeof displayName !== 'string' ? 'sc' : escape(displayName);
// Ensure that no displayName can lead to duplicate componentIds
identifiers[name] = (identifiers[name] || 0) + 1;
const componentId = `${name}-${generateComponentId(
// SC_VERSION gives us isolation between multiple runtimes on the page at once
// this is improved further with use of the babel plugin "namespace" feature
SC_VERSION + name + identifiers[name]
)}`;
return parentComponentId ? `${parentComponentId}-${componentId}` : componentId;
}
function useResolvedAttrs<Config>(theme: any = EMPTY_OBJECT, props: Config, attrs: Attrs) {
// NOTE: can't memoize this
// returns [context, resolvedAttrs]
// where resolvedAttrs is only the things injected by the attrs themselves
const context = { ...props, theme };
const resolvedAttrs = {};
attrs.forEach(attrDef => {
let resolvedAttrDef = attrDef;
let key;
if (isFunction(resolvedAttrDef)) {
resolvedAttrDef = resolvedAttrDef(context);
}
/* eslint-disable guard-for-in */
for (key in resolvedAttrDef) {
context[key] = resolvedAttrs[key] =
key === 'className'
? joinStrings(resolvedAttrs[key], resolvedAttrDef[key])
: resolvedAttrDef[key];
}
/* eslint-enable guard-for-in */
});
return [context, resolvedAttrs];
}
function useInjectedStyle<T>(
componentStyle: ComponentStyle,
isStatic: boolean,
resolvedAttrs: T,
warnTooManyClasses?: $Call<typeof createWarnTooManyClasses, string, string>
) {
const styleSheet = useStyleSheet();
const stylis = useStylis();
const className = isStatic
? componentStyle.generateAndInjectStyles(EMPTY_OBJECT, styleSheet, stylis)
: componentStyle.generateAndInjectStyles(resolvedAttrs, styleSheet, stylis);
if (process.env.NODE_ENV !== 'production' && !isStatic && warnTooManyClasses) {
warnTooManyClasses(className);
}
return className;
}
function useStyledComponentImpl(
forwardedComponent: IStyledComponent,
props: Object,
forwardedRef: Ref<any>,
isStatic: boolean
) {
const {
attrs: componentAttrs,
componentStyle,
defaultProps,
foldedComponentIds,
shouldForwardProp,
styledComponentId,
target,
} = forwardedComponent;
// NOTE: the non-hooks version only subscribes to this when !componentStyle.isStatic,
// but that'd be against the rules-of-hooks. We could be naughty and do it anyway as it
// should be an immutable value, but behave for now.
const theme = determineTheme(props, useContext(ThemeContext), defaultProps);
const [context, attrs] = useResolvedAttrs(theme || EMPTY_OBJECT, props, componentAttrs);
const generatedClassName = useInjectedStyle(
componentStyle,
isStatic,
context,
process.env.NODE_ENV !== 'production' ? forwardedComponent.warnTooManyClasses : undefined
);
const refToForward = forwardedRef;
const elementToBeCreated: Target = attrs.$as || props.$as || attrs.as || props.as || target;
const isTargetTag = isTag(elementToBeCreated);
const computedProps = attrs !== props ? { ...props, ...attrs } : props;
const propsForElement = {};
// eslint-disable-next-line guard-for-in
for (const key in computedProps) {
if (key[0] === '$' || key === 'as') continue;
else if (key === 'forwardedAs') {
propsForElement.as = computedProps[key];
} else if (
shouldForwardProp
? shouldForwardProp(key, validAttr, elementToBeCreated)
: isTargetTag
? validAttr(key)
: true
) {
// Don't pass through non HTML tags through to HTML elements
propsForElement[key] = computedProps[key];
}
}
if (props.style && attrs.style !== props.style) {
propsForElement.style = { ...props.style, ...attrs.style };
}
propsForElement.className = Array.prototype
.concat(
foldedComponentIds,
styledComponentId,
generatedClassName !== styledComponentId ? generatedClassName : null,
props.className,
attrs.className
)
.filter(Boolean)
.join(' ');
propsForElement.ref = refToForward;
return createElement(elementToBeCreated, propsForElement);
}
export default function createStyledComponent(
target: $PropertyType<IStyledComponent, 'target'>,
options: {
attrs?: Attrs,
componentId: string,
displayName?: string,
parentComponentId?: string,
shouldForwardProp?: ShouldForwardProp,
},
rules: RuleSet
) {
const isTargetStyledComp = isStyledComponent(target);
const isCompositeComponent = !isTag(target);
const {
attrs = EMPTY_ARRAY,
componentId = generateId(options.displayName, options.parentComponentId),
displayName = generateDisplayName(target),
} = options;
const styledComponentId =
options.displayName && options.componentId
? `${escape(options.displayName)}-${options.componentId}`
: options.componentId || componentId;
// fold the underlying StyledComponent attrs up (implicit extend)
const finalAttrs =
isTargetStyledComp && ((target: any): IStyledComponent).attrs
? Array.prototype.concat(((target: any): IStyledComponent).attrs, attrs).filter(Boolean)
: attrs;
// eslint-disable-next-line prefer-destructuring
let shouldForwardProp = options.shouldForwardProp;
if (isTargetStyledComp && target.shouldForwardProp) {
if (options.shouldForwardProp) {
// compose nested shouldForwardProp calls
shouldForwardProp = (prop, filterFn, elementToBeCreated) =>
((((target: any): IStyledComponent).shouldForwardProp: any): ShouldForwardProp)(
prop,
filterFn,
elementToBeCreated
) &&
((options.shouldForwardProp: any): ShouldForwardProp)(prop, filterFn, elementToBeCreated);
} else {
// eslint-disable-next-line prefer-destructuring
shouldForwardProp = ((target: any): IStyledComponent).shouldForwardProp;
}
}
const componentStyle = new ComponentStyle(
rules,
styledComponentId,
isTargetStyledComp ? ((target: Object).componentStyle: ComponentStyle) : undefined
);
// statically styled-components don't need to build an execution context object,
// and shouldn't be increasing the number of class names
const isStatic = componentStyle.isStatic && attrs.length === 0;
/**
* forwardRef creates a new interim component, which we'll take advantage of
* instead of extending ParentComponent to create _another_ interim class
*/
let WrappedStyledComponent: IStyledComponent;
const forwardRef = (props, ref) =>
// eslint-disable-next-line
useStyledComponentImpl(WrappedStyledComponent, props, ref, isStatic);
forwardRef.displayName = displayName;
WrappedStyledComponent = ((React.forwardRef(forwardRef): any): IStyledComponent);
WrappedStyledComponent.attrs = finalAttrs;
WrappedStyledComponent.componentStyle = componentStyle;
WrappedStyledComponent.displayName = displayName;
WrappedStyledComponent.shouldForwardProp = shouldForwardProp;
// this static is used to preserve the cascade of static classes for component selector
// purposes; this is especially important with usage of the css prop
WrappedStyledComponent.foldedComponentIds = isTargetStyledComp
? Array.prototype.concat(
((target: any): IStyledComponent).foldedComponentIds,
((target: any): IStyledComponent).styledComponentId
)
: EMPTY_ARRAY;
WrappedStyledComponent.styledComponentId = styledComponentId;
// fold the underlying StyledComponent target up since we folded the styles
WrappedStyledComponent.target = isTargetStyledComp
? ((target: any): IStyledComponent).target
: target;
WrappedStyledComponent.withComponent = function withComponent(tag: Target) {
const { componentId: previousComponentId, ...optionsToCopy } = options;
const newComponentId =
previousComponentId &&
`${previousComponentId}-${isTag(tag) ? tag : escape(getComponentName(tag))}`;
const newOptions = {
...optionsToCopy,
attrs: finalAttrs,
componentId: newComponentId,
};
return createStyledComponent(tag, newOptions, rules);
};
Object.defineProperty(WrappedStyledComponent, 'defaultProps', {
get() {
return this._foldedDefaultProps;
},
set(obj) {
this._foldedDefaultProps = isTargetStyledComp
? merge({}, ((target: any): IStyledComponent).defaultProps, obj)
: obj;
},
});
if (process.env.NODE_ENV !== 'production') {
checkDynamicCreation(displayName, styledComponentId);
WrappedStyledComponent.warnTooManyClasses = createWarnTooManyClasses(
displayName,
styledComponentId
);
}
// If the Object prototype is frozen, the "toString" property is non-writable. This means that any objects which inherit this property
// cannot have the property changed using an assignment. If using strict mode, attempting that will cause an error. If not using strict
// mode, attempting that will be silently ignored.
// However, we can still explicitly shadow the prototype's "toString" property by defining a new "toString" property on this object.
Object.defineProperty(WrappedStyledComponent, 'toString', { value: () => `.${WrappedStyledComponent.styledComponentId}` });
if (isCompositeComponent) {
hoist<
IStyledStatics,
$PropertyType<IStyledComponent, 'target'>,
{ [key: $Keys<IStyledStatics>]: true }
>(WrappedStyledComponent, ((target: any): $PropertyType<IStyledComponent, 'target'>), {
// all SC-specific things should not be hoisted
attrs: true,
componentStyle: true,
displayName: true,
foldedComponentIds: true,
shouldForwardProp: true,
styledComponentId: true,
target: true,
withComponent: true,
});
}
return WrappedStyledComponent;
}
+59
View File
@@ -0,0 +1,59 @@
// @flow
import React, { useContext, useMemo, type Element, type Context } from 'react';
import throwStyledError from '../utils/error';
import isFunction from '../utils/isFunction';
export type Theme = { [key: string]: mixed };
type ThemeArgument = Theme | ((outerTheme?: Theme) => Theme);
type Props = {
children?: Element<any>,
theme: ThemeArgument,
};
export const ThemeContext: Context<Theme | void> = React.createContext();
export const ThemeConsumer = ThemeContext.Consumer;
function mergeTheme(theme: ThemeArgument, outerTheme?: Theme): Theme {
if (!theme) {
return throwStyledError(14);
}
if (isFunction(theme)) {
const mergedTheme = theme(outerTheme);
if (
process.env.NODE_ENV !== 'production' &&
(mergedTheme === null || Array.isArray(mergedTheme) || typeof mergedTheme !== 'object')
) {
return throwStyledError(7);
}
return mergedTheme;
}
if (Array.isArray(theme) || typeof theme !== 'object') {
return throwStyledError(8);
}
return outerTheme ? { ...outerTheme, ...theme } : theme;
}
/**
* Provide a theme to an entire react component tree via context
*/
export default function ThemeProvider(props: Props) {
const outerTheme = useContext(ThemeContext);
const themeContext = useMemo(() => mergeTheme(props.theme, outerTheme), [
props.theme,
outerTheme,
]);
if (!props.children) {
return null;
}
return <ThemeContext.Provider value={themeContext}>{props.children}</ThemeContext.Provider>;
}
+51
View File
@@ -0,0 +1,51 @@
// @flow
import throwStyledError from '../utils/error';
const MAX_SMI = 1 << 31 - 1;
let groupIDRegister: Map<string, number> = new Map();
let reverseRegister: Map<number, string> = new Map();
let nextFreeGroup = 1;
export const resetGroupIds = () => {
groupIDRegister = new Map();
reverseRegister = new Map();
nextFreeGroup = 1;
};
export const getGroupForId = (id: string): number => {
if (groupIDRegister.has(id)) {
return (groupIDRegister.get(id): any);
}
while (reverseRegister.has(nextFreeGroup)) {
nextFreeGroup++;
}
const group = nextFreeGroup++;
if (
process.env.NODE_ENV !== 'production' &&
((group | 0) < 0 || group > MAX_SMI)
) {
throwStyledError(16, `${group}`);
}
groupIDRegister.set(id, group);
reverseRegister.set(group, id);
return group;
};
export const getIdForGroup = (group: number): void | string => {
return reverseRegister.get(group);
};
export const setGroupForId = (id: string, group: number) => {
if (group >= nextFreeGroup) {
nextFreeGroup = group + 1;
}
groupIDRegister.set(id, group);
reverseRegister.set(group, id);
};
+98
View File
@@ -0,0 +1,98 @@
// @flow
/* eslint-disable no-use-before-define */
import type { GroupedTag, Tag } from './types';
import { SPLITTER } from '../constants';
import throwStyledError from '../utils/error';
/** Create a GroupedTag with an underlying Tag implementation */
export const makeGroupedTag = (tag: Tag): GroupedTag => {
return new DefaultGroupedTag(tag);
};
const BASE_SIZE = 1 << 9;
class DefaultGroupedTag implements GroupedTag {
groupSizes: Uint32Array;
length: number;
tag: Tag;
constructor(tag: Tag) {
this.groupSizes = new Uint32Array(BASE_SIZE);
this.length = BASE_SIZE;
this.tag = tag;
}
indexOfGroup(group: number): number {
let index = 0;
for (let i = 0; i < group; i++) {
index += this.groupSizes[i];
}
return index;
}
insertRules(group: number, rules: string[]): void {
if (group >= this.groupSizes.length) {
const oldBuffer = this.groupSizes;
const oldSize = oldBuffer.length;
let newSize = oldSize;
while (group >= newSize) {
newSize <<= 1;
if (newSize < 0) {
throwStyledError(16, `${group}`);
}
}
this.groupSizes = new Uint32Array(newSize);
this.groupSizes.set(oldBuffer);
this.length = newSize;
for (let i = oldSize; i < newSize; i++) {
this.groupSizes[i] = 0;
}
}
let ruleIndex = this.indexOfGroup(group + 1);
for (let i = 0, l = rules.length; i < l; i++) {
if (this.tag.insertRule(ruleIndex, rules[i])) {
this.groupSizes[group]++;
ruleIndex++;
}
}
}
clearGroup(group: number): void {
if (group < this.length) {
const length = this.groupSizes[group];
const startIndex = this.indexOfGroup(group);
const endIndex = startIndex + length;
this.groupSizes[group] = 0;
for (let i = startIndex; i < endIndex; i++) {
this.tag.deleteRule(startIndex);
}
}
}
getGroup(group: number): string {
let css = '';
if (group >= this.length || this.groupSizes[group] === 0) {
return css;
}
const length = this.groupSizes[group];
const startIndex = this.indexOfGroup(group);
const endIndex = startIndex + length;
for (let i = startIndex; i < endIndex; i++) {
css += `${this.tag.getRule(i)}${SPLITTER}`;
}
return css;
}
}
+97
View File
@@ -0,0 +1,97 @@
// @flow
import { SPLITTER, SC_ATTR, SC_ATTR_ACTIVE, SC_ATTR_VERSION, SC_VERSION } from '../constants';
import { getIdForGroup, setGroupForId } from './GroupIDAllocator';
import type { Sheet } from './types';
const SELECTOR = `style[${SC_ATTR}][${SC_ATTR_VERSION}="${SC_VERSION}"]`;
const MARKER_RE = new RegExp(`^${SC_ATTR}\\.g(\\d+)\\[id="([\\w\\d-]+)"\\].*?"([^"]*)`);
export const outputSheet = (sheet: Sheet) => {
const tag = sheet.getTag();
const { length } = tag;
let css = '';
for (let group = 0; group < length; group++) {
const id = getIdForGroup(group);
if (id === undefined) continue;
const names = sheet.names.get(id);
const rules = tag.getGroup(group);
if (!names || !rules || !names.size) continue;
const selector = `${SC_ATTR}.g${group}[id="${id}"]`;
let content = '';
if (names !== undefined) {
names.forEach(name => {
if (name.length > 0) {
content += `${name},`;
}
});
}
// NOTE: It's easier to collect rules and have the marker
// after the actual rules to simplify the rehydration
css += `${rules}${selector}{content:"${content}"}${SPLITTER}`;
}
return css;
};
const rehydrateNamesFromContent = (sheet: Sheet, id: string, content: string) => {
const names = content.split(',');
let name;
for (let i = 0, l = names.length; i < l; i++) {
// eslint-disable-next-line
if ((name = names[i])) {
sheet.registerName(id, name);
}
}
};
const rehydrateSheetFromTag = (sheet: Sheet, style: HTMLStyleElement) => {
const parts = (style.textContent || '').split(SPLITTER);
const rules: string[] = [];
for (let i = 0, l = parts.length; i < l; i++) {
const part = parts[i].trim();
if (!part) continue;
const marker = part.match(MARKER_RE);
if (marker) {
const group = parseInt(marker[1], 10) | 0;
const id = marker[2];
if (group !== 0) {
// Rehydrate componentId to group index mapping
setGroupForId(id, group);
// Rehydrate names and rules
// looks like: data-styled.g11[id="idA"]{content:"nameA,"}
rehydrateNamesFromContent(sheet, id, marker[3]);
sheet.getTag().insertRules(group, rules);
}
rules.length = 0;
} else {
rules.push(part);
}
}
};
export const rehydrateSheet = (sheet: Sheet) => {
const nodes = document.querySelectorAll(SELECTOR);
for (let i = 0, l = nodes.length; i < l; i++) {
const node = ((nodes[i]: any): HTMLStyleElement);
if (node && node.getAttribute(SC_ATTR) !== SC_ATTR_ACTIVE) {
rehydrateSheetFromTag(sheet, node);
if (node.parentNode) {
node.parentNode.removeChild(node);
}
}
}
};
+129
View File
@@ -0,0 +1,129 @@
// @flow
import { DISABLE_SPEEDY, IS_BROWSER } from '../constants';
import { EMPTY_OBJECT } from '../utils/empties';
import { makeGroupedTag } from './GroupedTag';
import { getGroupForId } from './GroupIDAllocator';
import { outputSheet, rehydrateSheet } from './Rehydration';
import { makeTag } from './Tag';
import type { GroupedTag, Sheet, SheetOptions } from './types';
let SHOULD_REHYDRATE = IS_BROWSER;
type SheetConstructorArgs = {
isServer?: boolean,
useCSSOMInjection?: boolean,
target?: HTMLElement,
};
type GlobalStylesAllocationMap = { [key: string]: number };
type NamesAllocationMap = Map<string, Set<string>>;
const defaultOptions: SheetOptions = {
isServer: !IS_BROWSER,
useCSSOMInjection: !DISABLE_SPEEDY,
};
/** Contains the main stylesheet logic for stringification and caching */
export default class StyleSheet implements Sheet {
gs: GlobalStylesAllocationMap;
names: NamesAllocationMap;
options: SheetOptions;
server: boolean;
tag: void | GroupedTag;
/** Register a group ID to give it an index */
static registerId(id: string): number {
return getGroupForId(id);
}
constructor(
options: SheetConstructorArgs = EMPTY_OBJECT,
globalStyles?: GlobalStylesAllocationMap = {},
names?: NamesAllocationMap
) {
this.options = {
...defaultOptions,
...options,
};
this.gs = globalStyles;
this.names = new Map(names);
this.server = !!options.isServer;
// We rehydrate only once and use the sheet that is created first
if (!this.server && IS_BROWSER && SHOULD_REHYDRATE) {
SHOULD_REHYDRATE = false;
rehydrateSheet(this);
}
}
reconstructWithOptions(options: SheetConstructorArgs, withNames?: boolean = true) {
return new StyleSheet(
{ ...this.options, ...options },
this.gs,
(withNames && this.names) || undefined
);
}
allocateGSInstance(id: string) {
return (this.gs[id] = (this.gs[id] || 0) + 1);
}
/** Lazily initialises a GroupedTag for when it's actually needed */
getTag(): GroupedTag {
return this.tag || (this.tag = makeGroupedTag(makeTag(this.options)));
}
/** Check whether a name is known for caching */
hasNameForId(id: string, name: string): boolean {
return this.names.has(id) && (this.names.get(id): any).has(name);
}
/** Mark a group's name as known for caching */
registerName(id: string, name: string) {
getGroupForId(id);
if (!this.names.has(id)) {
const groupNames = new Set();
groupNames.add(name);
this.names.set(id, groupNames);
} else {
(this.names.get(id): any).add(name);
}
}
/** Insert new rules which also marks the name as known */
insertRules(id: string, name: string, rules: string[]) {
this.registerName(id, name);
this.getTag().insertRules(getGroupForId(id), rules);
}
/** Clears all cached names for a given group ID */
clearNames(id: string) {
if (this.names.has(id)) {
(this.names.get(id): any).clear();
}
}
/** Clears all rules for a given group ID */
clearRules(id: string) {
this.getTag().clearGroup(getGroupForId(id));
this.clearNames(id);
}
/** Clears the entire tag which deletes all rules but not its names */
clearTag() {
// NOTE: This does not clear the names, since it's only used during SSR
// so that we can continuously output only new rules
this.tag = undefined;
}
/** Outputs the current sheet as a CSS string with markers for SSR */
toString(): string {
return outputSheet(this);
}
}
+134
View File
@@ -0,0 +1,134 @@
// @flow
/* eslint-disable no-use-before-define */
import { makeStyleTag, getSheet } from './dom';
import type { SheetOptions, Tag } from './types';
/** Create a CSSStyleSheet-like tag depending on the environment */
export const makeTag = ({ isServer, useCSSOMInjection, target }: SheetOptions): Tag => {
if (isServer) {
return new VirtualTag(target);
} else if (useCSSOMInjection) {
return new CSSOMTag(target);
} else {
return new TextTag(target);
}
};
export class CSSOMTag implements Tag {
element: HTMLStyleElement;
sheet: CSSStyleSheet;
length: number;
constructor(target?: HTMLElement) {
const element = (this.element = makeStyleTag(target));
// Avoid Edge bug where empty style elements don't create sheets
element.appendChild(document.createTextNode(''));
this.sheet = getSheet(element);
this.length = 0;
}
insertRule(index: number, rule: string): boolean {
try {
this.sheet.insertRule(rule, index);
this.length++;
return true;
} catch (_error) {
return false;
}
}
deleteRule(index: number): void {
this.sheet.deleteRule(index);
this.length--;
}
getRule(index: number): string {
const rule = this.sheet.cssRules[index];
// Avoid IE11 quirk where cssText is inaccessible on some invalid rules
if (rule !== undefined && typeof rule.cssText === 'string') {
return rule.cssText;
} else {
return '';
}
}
}
/** A Tag that emulates the CSSStyleSheet API but uses text nodes */
export class TextTag implements Tag {
element: HTMLStyleElement;
nodes: NodeList<Node>;
length: number;
constructor(target?: HTMLElement) {
const element = (this.element = makeStyleTag(target));
this.nodes = element.childNodes;
this.length = 0;
}
insertRule(index: number, rule: string): boolean {
if (index <= this.length && index >= 0) {
const node = document.createTextNode(rule);
const refNode = this.nodes[index];
this.element.insertBefore(node, refNode || null);
this.length++;
return true;
} else {
return false;
}
}
deleteRule(index: number): void {
this.element.removeChild(this.nodes[index]);
this.length--;
}
getRule(index: number): string {
if (index < this.length) {
return this.nodes[index].textContent;
} else {
return '';
}
}
}
/** A completely virtual (server-side) Tag that doesn't manipulate the DOM */
export class VirtualTag implements Tag {
rules: string[];
length: number;
constructor(_target?: HTMLElement) {
this.rules = [];
this.length = 0;
}
insertRule(index: number, rule: string): boolean {
if (index <= this.length) {
this.rules.splice(index, 0, rule);
this.length++;
return true;
} else {
return false;
}
}
deleteRule(index: number): void {
this.rules.splice(index, 1);
this.length--;
}
getRule(index: number): string {
if (index < this.length) {
return this.rules[index];
} else {
return '';
}
}
}
+60
View File
@@ -0,0 +1,60 @@
// @flow
import { SC_ATTR, SC_ATTR_ACTIVE, SC_ATTR_VERSION, SC_VERSION } from '../constants';
import getNonce from '../utils/nonce';
import throwStyledError from '../utils/error';
const ELEMENT_TYPE = 1; /* Node.ELEMENT_TYPE */
/** Find last style element if any inside target */
const findLastStyleTag = (target: HTMLElement): void | HTMLStyleElement => {
const { childNodes } = target;
for (let i = childNodes.length; i >= 0; i--) {
const child = ((childNodes[i]: any): ?HTMLElement);
if (child && child.nodeType === ELEMENT_TYPE && child.hasAttribute(SC_ATTR)) {
return ((child: any): HTMLStyleElement);
}
}
return undefined;
};
/** Create a style element inside `target` or <head> after the last */
export const makeStyleTag = (target?: HTMLElement): HTMLStyleElement => {
const head = ((document.head: any): HTMLElement);
const parent = target || head;
const style = document.createElement('style');
const prevStyle = findLastStyleTag(parent);
const nextSibling = prevStyle !== undefined ? prevStyle.nextSibling : null;
style.setAttribute(SC_ATTR, SC_ATTR_ACTIVE);
style.setAttribute(SC_ATTR_VERSION, SC_VERSION);
const nonce = getNonce();
if (nonce) style.setAttribute('nonce', nonce);
parent.insertBefore(style, nextSibling);
return style;
};
/** Get the CSSStyleSheet instance for a given style element */
export const getSheet = (tag: HTMLStyleElement): CSSStyleSheet => {
if (tag.sheet) {
return ((tag.sheet: any): CSSStyleSheet);
}
// Avoid Firefox quirk where the style element might not have a sheet property
const { styleSheets } = document;
for (let i = 0, l = styleSheets.length; i < l; i++) {
const sheet = styleSheets[i];
if (sheet.ownerNode === tag) {
return ((sheet: any): CSSStyleSheet);
}
}
throwStyledError(17);
return (undefined: any);
};
+17
View File
@@ -0,0 +1,17 @@
// @flow
import unitless from '@emotion/unitless';
// Taken from https://github.com/facebook/react/blob/b87aabdfe1b7461e7331abb3601d9e6bb27544bc/packages/react-dom/src/shared/dangerousStyleValue.js
export default function addUnitIfNeeded(name: string, value: any): any {
// https://github.com/amilajack/eslint-plugin-flowtype-errors/issues/133
// $FlowFixMe
if (value == null || typeof value === 'boolean' || value === '') {
return '';
}
if (typeof value === 'number' && value !== 0 && !(name in unitless) && !name.startsWith('--')) {
return `${value}px`; // Presumes implicit 'px' suffix for unitless numbers except for CSS variables
}
return String(value).trim();
}
@@ -0,0 +1,57 @@
// @flow
import { useRef } from 'react';
const invalidHookCallRe = /invalid hook call/i;
const seen = new Set();
export const checkDynamicCreation = (displayName: string, componentId?: string) => {
if (process.env.NODE_ENV !== 'production') {
const parsedIdString = componentId ? ` with the id of "${componentId}"` : '';
const message =
`The component ${displayName}${parsedIdString} has been created dynamically.\n` +
"You may see this warning because you've called styled inside another component.\n" +
'To resolve this only create new StyledComponents outside of any render method and function component.';
// If a hook is called outside of a component:
// React 17 and earlier throw an error
// React 18 and above use console.error
const originalConsoleError = console.error // eslint-disable-line no-console
try {
let didNotCallInvalidHook = true
/* $FlowIgnore[cannot-write] */
console.error = (consoleErrorMessage, ...consoleErrorArgs) => { // eslint-disable-line no-console
// The error here is expected, since we're expecting anything that uses `checkDynamicCreation` to
// be called outside of a React component.
if (invalidHookCallRe.test(consoleErrorMessage)) {
didNotCallInvalidHook = false
// This shouldn't happen, but resets `warningSeen` if we had this error happen intermittently
seen.delete(message);
} else {
originalConsoleError(consoleErrorMessage, ...consoleErrorArgs);
}
}
// We purposefully call `useRef` outside of a component and expect it to throw
// If it doesn't, then we're inside another component.
// eslint-disable-next-line react-hooks/rules-of-hooks
useRef();
if (didNotCallInvalidHook && !seen.has(message)) {
// eslint-disable-next-line no-console
console.warn(message);
seen.add(message);
}
} catch (error) {
// The error here is expected, since we're expecting anything that uses `checkDynamicCreation` to
// be called outside of a React component.
if (invalidHookCallRe.test(error.message)) {
// This shouldn't happen, but resets `warningSeen` if we had this error happen intermittently
seen.delete(message);
}
} finally {
/* $FlowIgnore[cannot-write] */
console.error = originalConsoleError; // eslint-disable-line no-console
}
}
};
+10
View File
@@ -0,0 +1,10 @@
// @flow
import { EMPTY_OBJECT } from './empties';
type Props = {
theme?: any,
};
export default (props: Props, providedTheme: any, defaultProps: any = EMPTY_OBJECT) => {
return (props.theme !== defaultProps.theme && props.theme) || providedTheme || defaultProps.theme;
};
+143
View File
@@ -0,0 +1,143 @@
// @flow
// Thanks to ReactDOMFactories for this handy list!
export default [
'a',
'abbr',
'address',
'area',
'article',
'aside',
'audio',
'b',
'base',
'bdi',
'bdo',
'big',
'blockquote',
'body',
'br',
'button',
'canvas',
'caption',
'cite',
'code',
'col',
'colgroup',
'data',
'datalist',
'dd',
'del',
'details',
'dfn',
'dialog',
'div',
'dl',
'dt',
'em',
'embed',
'fieldset',
'figcaption',
'figure',
'footer',
'form',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'head',
'header',
'hgroup',
'hr',
'html',
'i',
'iframe',
'img',
'input',
'ins',
'kbd',
'keygen',
'label',
'legend',
'li',
'link',
'main',
'map',
'mark',
'marquee',
'menu',
'menuitem',
'meta',
'meter',
'nav',
'noscript',
'object',
'ol',
'optgroup',
'option',
'output',
'p',
'param',
'picture',
'pre',
'progress',
'q',
'rp',
'rt',
'ruby',
's',
'samp',
'script',
'section',
'select',
'small',
'source',
'span',
'strong',
'style',
'sub',
'summary',
'sup',
'table',
'tbody',
'td',
'textarea',
'tfoot',
'th',
'thead',
'time',
'title',
'tr',
'track',
'u',
'ul',
'var',
'video',
'wbr',
// SVG
'circle',
'clipPath',
'defs',
'ellipse',
'foreignObject',
'g',
'image',
'line',
'linearGradient',
'marker',
'mask',
'path',
'pattern',
'polygon',
'polyline',
'radialGradient',
'rect',
'stop',
'svg',
'text',
'textPath',
'tspan',
];
+3
View File
@@ -0,0 +1,3 @@
// @flow
export const EMPTY_ARRAY = Object.freeze([]);
export const EMPTY_OBJECT = Object.freeze({});
+41
View File
@@ -0,0 +1,41 @@
// @flow
import errorMap from './errors';
const ERRORS = process.env.NODE_ENV !== 'production' ? errorMap : {};
/**
* super basic version of sprintf
*/
function format(...args) {
let a = args[0];
const b = [];
for (let c = 1, len = args.length; c < len; c += 1) {
b.push(args[c]);
}
b.forEach(d => {
a = a.replace(/%[a-z]/, d);
});
return a;
}
/**
* Create an error file out of errors.md for development and a simple web link to the full errors
* in production mode.
*/
export default function throwStyledComponentsError(
code: string | number,
...interpolations: Array<any>
) {
if (process.env.NODE_ENV === 'production') {
throw new Error(
`An error occurred. See https://git.io/JUIaE#${code} for more information.${
interpolations.length > 0 ? ` Args: ${interpolations.join(', ')}` : ''
}`
);
} else {
throw new Error(format(ERRORS[code], ...interpolations).trim());
}
}
+22
View File
@@ -0,0 +1,22 @@
// @flow
// Source: https://www.w3.org/TR/cssom-1/#serialize-an-identifier
// Control characters and non-letter first symbols are not supported
const escapeRegex = /[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~-]+/g;
const dashesAtEnds = /(^-|-$)/g;
/**
* TODO: Explore using CSS.escape when it becomes more available
* in evergreen browsers.
*/
export default function escape(str: string): string {
return (
str
// Replace all possible CSS selectors
.replace(escapeRegex, '-')
// Remove extraneous hyphens at the start and end
.replace(dashesAtEnds, '')
);
}
+92
View File
@@ -0,0 +1,92 @@
// @flow
import { isElement } from 'react-is';
import getComponentName from './getComponentName';
import isFunction from './isFunction';
import isStatelessFunction from './isStatelessFunction';
import isPlainObject from './isPlainObject';
import isStyledComponent from './isStyledComponent';
import Keyframes from '../models/Keyframes';
import hyphenate from './hyphenateStyleName';
import addUnitIfNeeded from './addUnitIfNeeded';
import { type Stringifier } from '../types';
/**
* It's falsish not falsy because 0 is allowed.
*/
const isFalsish = chunk => chunk === undefined || chunk === null || chunk === false || chunk === '';
export const objToCssArray = (obj: Object, prevKey?: string): Array<string | Function> => {
const rules = [];
for (const key in obj) {
if (!obj.hasOwnProperty(key) || isFalsish(obj[key])) continue;
if ((Array.isArray(obj[key]) && obj[key].isCss) || isFunction(obj[key])) {
rules.push(`${hyphenate(key)}:`, obj[key], ';');
} else if (isPlainObject(obj[key])) {
rules.push(...objToCssArray(obj[key], key));
} else {
rules.push(`${hyphenate(key)}: ${addUnitIfNeeded(key, obj[key])};`);
}
}
return prevKey ? [`${prevKey} {`, ...rules, '}'] : rules;
};
export default function flatten(
chunk: any,
executionContext: ?Object,
styleSheet: ?Object,
stylisInstance: ?Stringifier
): any {
if (Array.isArray(chunk)) {
const ruleSet = [];
for (let i = 0, len = chunk.length, result; i < len; i += 1) {
result = flatten(chunk[i], executionContext, styleSheet, stylisInstance);
if (result === '') continue;
else if (Array.isArray(result)) ruleSet.push(...result);
else ruleSet.push(result);
}
return ruleSet;
}
if (isFalsish(chunk)) {
return '';
}
/* Handle other components */
if (isStyledComponent(chunk)) {
return `.${chunk.styledComponentId}`;
}
/* Either execute or defer the function */
if (isFunction(chunk)) {
if (isStatelessFunction(chunk) && executionContext) {
const result = chunk(executionContext);
if (process.env.NODE_ENV !== 'production' && isElement(result)) {
// eslint-disable-next-line no-console
console.warn(
`${getComponentName(
chunk
)} is not a styled component and cannot be referred to via component selector. See https://www.styled-components.com/docs/advanced#referring-to-other-components for more details.`
);
}
return flatten(result, executionContext, styleSheet, stylisInstance);
} else return chunk;
}
if (chunk instanceof Keyframes) {
if (styleSheet) {
chunk.inject(styleSheet, stylisInstance);
return chunk.getName(stylisInstance);
} else return chunk;
}
/* Handle objects */
return isPlainObject(chunk) ? objToCssArray(chunk) : chunk.toString();
}
@@ -0,0 +1,25 @@
// @flow
/* eslint-disable no-bitwise */
const AD_REPLACER_R = /(a)(d)/gi;
/* This is the "capacity" of our alphabet i.e. 2x26 for all letters plus their capitalised
* counterparts */
const charsLength = 52;
/* start at 75 for 'a' until 'z' (25) and then start at 65 for capitalised letters */
const getAlphabeticChar = (code: number): string =>
String.fromCharCode(code + (code > 25 ? 39 : 97));
/* input a number, usually a hash and convert it to base-52 */
export default function generateAlphabeticName(code: number): string {
let name = '';
let x;
/* get a char and divide by alphabet-length */
for (x = Math.abs(code); x > charsLength; x = (x / charsLength) | 0) {
name = getAlphabeticChar(x % charsLength) + name;
}
return (getAlphabeticChar(x % charsLength) + name).replace(AD_REPLACER_R, '$1-$2');
}
@@ -0,0 +1,8 @@
// @flow
/* eslint-disable */
import generateAlphabeticName from './generateAlphabeticName';
import { hash } from './hash';
export default (str: string): string => {
return generateAlphabeticName(hash(str) >>> 0);
};
@@ -0,0 +1,10 @@
// @flow
import type { IStyledComponent } from '../types';
import getComponentName from './getComponentName';
import isTag from './isTag';
export default function generateDisplayName(
target: $PropertyType<IStyledComponent, 'target'>
): string {
return isTag(target) ? `styled.${target}` : `Styled(${getComponentName(target)})`;
}
+15
View File
@@ -0,0 +1,15 @@
// @flow
import type { IStyledComponent } from '../types';
export default function getComponentName(
target: $PropertyType<IStyledComponent, 'target'>
): string {
return (
(process.env.NODE_ENV !== 'production' ? typeof target === 'string' && target : false) ||
// $FlowFixMe
target.displayName ||
// $FlowFixMe
target.name ||
'Component'
);
}
+22
View File
@@ -0,0 +1,22 @@
// @flow
/* eslint-disable */
export const SEED = 5381;
// When we have separate strings it's useful to run a progressive
// version of djb2 where we pretend that we're still looping over
// the same string
export const phash = (h: number, x: string): number => {
let i = x.length;
while (i) {
h = (h * 33) ^ x.charCodeAt(--i);
}
return h;
};
// This is a djb2 hashing function
export const hash = (x: string): number => {
return phash(SEED, x);
};
@@ -0,0 +1,35 @@
// @flow
/**
* inlined version of
* https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/hyphenateStyleName.js
*/
const uppercaseCheck = /([A-Z])/;
const uppercasePattern = /([A-Z])/g;
const msPattern = /^ms-/;
const prefixAndLowerCase = (char: string): string => `-${char.toLowerCase()}`;
/**
* Hyphenates a camelcased CSS property name, for example:
*
* > hyphenateStyleName('backgroundColor')
* < "background-color"
* > hyphenateStyleName('MozTransition')
* < "-moz-transition"
* > hyphenateStyleName('msTransition')
* < "-ms-transition"
*
* As Modernizr suggests (http://modernizr.com/docs/#prefixed), an `ms` prefix
* is converted to `-ms-`.
*
* @param {string} string
* @return {string}
*/
export default function hyphenateStyleName(string: string): string {
return uppercaseCheck.test(string)
? string
.replace(uppercasePattern, prefixAndLowerCase)
.replace(msPattern, '-ms-')
: string;
}
+15
View File
@@ -0,0 +1,15 @@
// @flow
import type { Interpolation } from '../types';
export default (
strings: Array<string>,
interpolations: Array<Interpolation>
): Array<Interpolation> => {
const result = [strings[0]];
for (let i = 0, len = interpolations.length; i < len; i += 1) {
result.push(interpolations[i], strings[i + 1]);
}
return result;
};
+4
View File
@@ -0,0 +1,4 @@
// @flow
export default function isFunction(test: any): boolean %checks {
return typeof test === 'function';
}
+8
View File
@@ -0,0 +1,8 @@
// @flow
import { typeOf } from 'react-is';
export default (x: any): boolean =>
x !== null &&
typeof x === 'object' &&
(x.toString ? x.toString() : Object.prototype.toString.call(x)) === '[object Object]' &&
!typeOf(x);
@@ -0,0 +1,10 @@
// @flow
export default function isStatelessFunction(test: any): boolean {
return (
typeof test === 'function'
&& !(
test.prototype
&& test.prototype.isReactComponent
)
);
}
+18
View File
@@ -0,0 +1,18 @@
// @flow
import isFunction from './isFunction';
import isStyledComponent from './isStyledComponent';
import type { RuleSet } from '../types';
export default function isStaticRules(rules: RuleSet): boolean {
for (let i = 0; i < rules.length; i += 1) {
const rule = rules[i];
if (isFunction(rule) && !isStyledComponent(rule)) {
// functions are allowed to be static if they're just being
// used to get the classname of a nested styled component
return false;
}
}
return true;
}
@@ -0,0 +1,4 @@
// @flow
export default function isStyledComponent(target: any): boolean %checks {
return target && typeof target.styledComponentId === 'string';
}
+11
View File
@@ -0,0 +1,11 @@
// @flow
import type { IStyledComponent } from '../types';
export default function isTag(target: $PropertyType<IStyledComponent, 'target'>): boolean %checks {
return (
typeof target === 'string' &&
(process.env.NODE_ENV !== 'production'
? target.charAt(0) === target.charAt(0).toLowerCase()
: true)
);
}
+6
View File
@@ -0,0 +1,6 @@
/**
* Convenience function for joining strings to form className chains
*/
export default function joinStrings(a: ?String, b: ?String): ?String {
return a && b ? `${a} ${b}` : a || b;
}
+60
View File
@@ -0,0 +1,60 @@
/* eslint-disable */
/**
mixin-deep; https://github.com/jonschlinkert/mixin-deep
Inlined such that it will be consistently transpiled to an IE-compatible syntax.
The MIT License (MIT)
Copyright (c) 2014-present, Jon Schlinkert.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
const isObject = val => {
return (
typeof val === 'function' || (typeof val === 'object' && val !== null && !Array.isArray(val))
);
};
const isValidKey = key => {
return key !== '__proto__' && key !== 'constructor' && key !== 'prototype';
};
function mixin(target, val, key) {
const obj = target[key];
if (isObject(val) && isObject(obj)) {
mixinDeep(obj, val);
} else {
target[key] = val;
}
}
export default function mixinDeep(target, ...rest) {
for (const obj of rest) {
if (isObject(obj)) {
for (const key in obj) {
if (isValidKey(key)) {
mixin(target, obj[key], key);
}
}
}
}
return target;
}
+10
View File
@@ -0,0 +1,10 @@
// @flow
/* eslint-disable camelcase, no-undef */
declare var __webpack_nonce__: string;
const getNonce = () => {
return typeof __webpack_nonce__ !== 'undefined' ? __webpack_nonce__ : null;
};
export default getNonce;
+109
View File
@@ -0,0 +1,109 @@
import Stylis from '@emotion/stylis';
import { type Stringifier } from '../types';
import { EMPTY_ARRAY, EMPTY_OBJECT } from './empties';
import throwStyledError from './error';
import { phash, SEED } from './hash';
import insertRulePlugin from './stylisPluginInsertRule';
const COMMENT_REGEX = /^\s*\/\/.*$/gm;
const COMPLEX_SELECTOR_PREFIX = [':', '[', '.', '#'];
type StylisInstanceConstructorArgs = {
options?: Object,
plugins?: Array<Function>,
};
export default function createStylisInstance({
options = EMPTY_OBJECT,
plugins = EMPTY_ARRAY,
}: StylisInstanceConstructorArgs = EMPTY_OBJECT) {
const stylis = new Stylis(options);
// Wrap `insertRulePlugin to build a list of rules,
// and then make our own plugin to return the rules. This
// makes it easier to hook into the existing SSR architecture
let parsingRules = [];
// eslint-disable-next-line consistent-return
const returnRulesPlugin = context => {
if (context === -2) {
const parsedRules = parsingRules;
parsingRules = [];
return parsedRules;
}
};
const parseRulesPlugin = insertRulePlugin(rule => {
parsingRules.push(rule);
});
let _componentId: string;
let _selector: string;
let _selectorRegexp: RegExp;
let _consecutiveSelfRefRegExp: RegExp;
const selfReferenceReplacer = (match, offset, string) => {
if (
// do not replace the first occurrence if it is complex (has a modifier)
(offset === 0 ? COMPLEX_SELECTOR_PREFIX.indexOf(string[_selector.length]) === -1 : true) &&
// no consecutive self refs (.b.b); that is a precedence boost and treated differently
!string.match(_consecutiveSelfRefRegExp)
) {
return `.${_componentId}`;
}
return match;
};
/**
* When writing a style like
*
* & + & {
* color: red;
* }
*
* The second ampersand should be a reference to the static component class. stylis
* has no knowledge of static class so we have to intelligently replace the base selector.
*
* https://github.com/thysultan/stylis.js/tree/v3.5.4#plugins <- more info about the context phase values
* "2" means this plugin is taking effect at the very end after all other processing is complete
*/
const selfReferenceReplacementPlugin = (context, _, selectors) => {
if (context === 2 && selectors.length && selectors[0].lastIndexOf(_selector) > 0) {
// eslint-disable-next-line no-param-reassign
selectors[0] = selectors[0].replace(_selectorRegexp, selfReferenceReplacer);
}
};
stylis.use([...plugins, selfReferenceReplacementPlugin, parseRulesPlugin, returnRulesPlugin]);
function stringifyRules(css, selector, prefix, componentId = '&'): Stringifier {
const flatCSS = css.replace(COMMENT_REGEX, '');
const cssStr = selector && prefix ? `${prefix} ${selector} { ${flatCSS} }` : flatCSS;
// stylis has no concept of state to be passed to plugins
// but since JS is single-threaded, we can rely on that to ensure
// these properties stay in sync with the current stylis run
_componentId = componentId;
_selector = selector;
_selectorRegexp = new RegExp(`\\${_selector}\\b`, 'g');
_consecutiveSelfRefRegExp = new RegExp(`(\\${_selector}\\b){2,}`);
return stylis(prefix || !selector ? '' : selector, cssStr);
}
stringifyRules.hash = plugins.length
? plugins
.reduce((acc, plugin) => {
if (!plugin.name) {
throwStyledError(15);
}
return phash(acc, plugin.name);
}, SEED)
.toString()
: '';
return stringifyRules;
}
@@ -0,0 +1,71 @@
/**
* MIT License
*
* Copyright (c) 2016 Sultan Tarimo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* eslint-disable */
export default function(insertRule) {
const delimiter = '/*|*/';
const needle = `${delimiter}}`;
function toSheet(block) {
if (block) {
try {
insertRule(`${block}}`);
} catch (e) {}
}
}
return function ruleSheet(
context,
content,
selectors,
parents,
line,
column,
length,
ns,
depth,
at
) {
switch (context) {
// property
case 1:
// @import
if (depth === 0 && content.charCodeAt(0) === 64) return insertRule(`${content};`), '';
break;
// selector
case 2:
if (ns === 0) return content + delimiter;
break;
// at-rule
case 3:
switch (ns) {
// @font-face, @page
case 102:
case 112:
return insertRule(selectors[0] + content), '';
default:
return content + (at === 0 ? delimiter : '');
}
case -2:
content.split(needle).forEach(toSheet);
}
};
}