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:
Generated
Vendored
+50
@@ -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
@@ -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
@@ -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 = {};
|
||||
+34
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
}
|
||||
+76
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
+57
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
export const EMPTY_ARRAY = Object.freeze([]);
|
||||
export const EMPTY_OBJECT = Object.freeze({});
|
||||
+41
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
+25
@@ -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');
|
||||
}
|
||||
+8
@@ -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);
|
||||
};
|
||||
+10
@@ -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
@@ -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
@@ -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);
|
||||
};
|
||||
+35
@@ -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
@@ -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
@@ -0,0 +1,4 @@
|
||||
// @flow
|
||||
export default function isFunction(test: any): boolean %checks {
|
||||
return typeof test === 'function';
|
||||
}
|
||||
+8
@@ -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);
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// @flow
|
||||
export default function isStatelessFunction(test: any): boolean {
|
||||
return (
|
||||
typeof test === 'function'
|
||||
&& !(
|
||||
test.prototype
|
||||
&& test.prototype.isReactComponent
|
||||
)
|
||||
);
|
||||
}
|
||||
+18
@@ -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;
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// @flow
|
||||
export default function isStyledComponent(target: any): boolean %checks {
|
||||
return target && typeof target.styledComponentId === 'string';
|
||||
}
|
||||
+11
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
+71
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user