react-intl 6.0.5 → 6.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/BUILD +130 -0
  2. package/CHANGELOG.md +1384 -0
  3. package/LICENSE.md +0 -0
  4. package/README.md +0 -0
  5. package/example-sandboxes/rescripts/.rescriptsrc.js +28 -0
  6. package/example-sandboxes/rescripts/package-lock.json +18035 -0
  7. package/example-sandboxes/rescripts/package.json +35 -0
  8. package/example-sandboxes/rescripts/public/index.html +42 -0
  9. package/example-sandboxes/rescripts/src/App.tsx +16 -0
  10. package/example-sandboxes/rescripts/src/index.tsx +5 -0
  11. package/example-sandboxes/rescripts/src/react-app-env.d.ts +1 -0
  12. package/example-sandboxes/rescripts/src/styles.css +4 -0
  13. package/example-sandboxes/rescripts/tsconfig.json +20 -0
  14. package/example-sandboxes/strict-locale-type/.env +1 -0
  15. package/example-sandboxes/strict-locale-type/package-lock.json +14595 -0
  16. package/example-sandboxes/strict-locale-type/package.json +35 -0
  17. package/example-sandboxes/strict-locale-type/src/App.tsx +48 -0
  18. package/example-sandboxes/strict-locale-type/src/index.html +28 -0
  19. package/example-sandboxes/strict-locale-type/src/index.tsx +7 -0
  20. package/example-sandboxes/strict-locale-type/src/styles.css +4 -0
  21. package/example-sandboxes/strict-locale-type/tsconfig.json +7 -0
  22. package/example-sandboxes/strict-message-types/.env +1 -0
  23. package/example-sandboxes/strict-message-types/package-lock.json +14596 -0
  24. package/example-sandboxes/strict-message-types/package.json +35 -0
  25. package/example-sandboxes/strict-message-types/src/App.tsx +31 -0
  26. package/example-sandboxes/strict-message-types/src/index.html +28 -0
  27. package/example-sandboxes/strict-message-types/src/index.tsx +7 -0
  28. package/example-sandboxes/strict-message-types/src/styles.css +4 -0
  29. package/example-sandboxes/strict-message-types/tsconfig.json +7 -0
  30. package/examples/BUILD +70 -0
  31. package/examples/Bug2727.tsx +37 -0
  32. package/examples/HandleChange.tsx +48 -0
  33. package/examples/Hooks.tsx +126 -0
  34. package/examples/Injected.tsx +41 -0
  35. package/examples/Messages.tsx +82 -0
  36. package/examples/StaticTypeSafetyAndCodeSplitting/StaticTypeSafetyAndCodeSplitting.tsx +44 -0
  37. package/examples/StaticTypeSafetyAndCodeSplitting/en.json +3 -0
  38. package/examples/StaticTypeSafetyAndCodeSplitting/intlHelpers.tsx +39 -0
  39. package/examples/StaticTypeSafetyAndCodeSplitting/it.json +3 -0
  40. package/examples/TimeZone.tsx +44 -0
  41. package/examples/advanced/Advanced.tsx +68 -0
  42. package/examples/advanced/compiled-lang/en.json +77 -0
  43. package/examples/advanced/compiled-lang/fr.json +77 -0
  44. package/examples/index.html +20 -0
  45. package/examples/index.tsx +44 -0
  46. package/examples/package.json +20 -0
  47. package/index.ts +127 -0
  48. package/jest.config.js +27 -0
  49. package/package.json +12 -8
  50. package/src/components/createFormattedComponent.tsx +123 -0
  51. package/src/components/dateTimeRange.tsx +26 -0
  52. package/src/components/injectIntl.tsx +111 -0
  53. package/src/components/message.tsx +73 -0
  54. package/src/components/plural.tsx +45 -0
  55. package/src/components/provider.tsx +196 -0
  56. package/src/components/relative.tsx +192 -0
  57. package/src/components/useIntl.ts +10 -0
  58. package/src/types.ts +29 -0
  59. package/src/utils.ts +77 -0
  60. package/tests/functional/index.ts +18 -0
  61. package/tests/functional/support/build.ts +16 -0
  62. package/tests/functional/support/format.tsx +112 -0
  63. package/tests/perf/index.tsx +196 -0
  64. package/tests/setup.js +10 -0
  65. package/tests/unit/components/__snapshots__/displayName.tsx.snap +19 -0
  66. package/tests/unit/components/__snapshots__/message.tsx.snap +41 -0
  67. package/tests/unit/components/__snapshots__/relative.tsx.snap +11 -0
  68. package/tests/unit/components/__snapshots__/useIntl.tsx.snap +25 -0
  69. package/tests/unit/components/date.tsx +233 -0
  70. package/tests/unit/components/dateTimeRange.tsx +103 -0
  71. package/tests/unit/components/displayName.tsx +57 -0
  72. package/tests/unit/components/message.tsx +509 -0
  73. package/tests/unit/components/number.tsx +198 -0
  74. package/tests/unit/components/plural.tsx +116 -0
  75. package/tests/unit/components/provider.tsx +215 -0
  76. package/tests/unit/components/relative.tsx +263 -0
  77. package/tests/unit/components/time.tsx +242 -0
  78. package/tests/unit/components/useIntl.tsx +64 -0
  79. package/tests/unit/components/withIntl.tsx +66 -0
  80. package/tests/unit/react-intl.ts +88 -0
  81. package/tests/unit/testUtils.tsx +42 -0
  82. package/tsconfig.json +5 -0
  83. package/index.d.ts +0 -43
  84. package/index.d.ts.map +0 -1
  85. package/index.js +0 -51
  86. package/lib/index.d.ts +0 -43
  87. package/lib/index.d.ts.map +0 -1
  88. package/lib/index.js +0 -26
  89. package/lib/src/components/createFormattedComponent.d.ts +0 -29
  90. package/lib/src/components/createFormattedComponent.d.ts.map +0 -1
  91. package/lib/src/components/createFormattedComponent.js +0 -62
  92. package/lib/src/components/dateTimeRange.d.ts +0 -11
  93. package/lib/src/components/dateTimeRange.d.ts.map +0 -1
  94. package/lib/src/components/dateTimeRange.js +0 -15
  95. package/lib/src/components/injectIntl.d.ts +0 -22
  96. package/lib/src/components/injectIntl.d.ts.map +0 -1
  97. package/lib/src/components/injectIntl.js +0 -29
  98. package/lib/src/components/message.d.ts +0 -12
  99. package/lib/src/components/message.d.ts.map +0 -1
  100. package/lib/src/components/message.js +0 -35
  101. package/lib/src/components/plural.d.ts +0 -15
  102. package/lib/src/components/plural.d.ts.map +0 -1
  103. package/lib/src/components/plural.js +0 -26
  104. package/lib/src/components/provider.d.ts +0 -35
  105. package/lib/src/components/provider.d.ts.map +0 -1
  106. package/lib/src/components/provider.js +0 -108
  107. package/lib/src/components/relative.d.ts +0 -12
  108. package/lib/src/components/relative.d.ts.map +0 -1
  109. package/lib/src/components/relative.js +0 -129
  110. package/lib/src/components/useIntl.d.ts +0 -3
  111. package/lib/src/components/useIntl.d.ts.map +0 -1
  112. package/lib/src/components/useIntl.js +0 -8
  113. package/lib/src/types.d.ts +0 -12
  114. package/lib/src/types.d.ts.map +0 -1
  115. package/lib/src/types.js +0 -1
  116. package/lib/src/utils.d.ts +0 -14
  117. package/lib/src/utils.d.ts.map +0 -1
  118. package/lib/src/utils.js +0 -43
  119. package/react-intl.iife.js +0 -7485
  120. package/src/components/createFormattedComponent.d.ts +0 -29
  121. package/src/components/createFormattedComponent.d.ts.map +0 -1
  122. package/src/components/createFormattedComponent.js +0 -69
  123. package/src/components/dateTimeRange.d.ts +0 -11
  124. package/src/components/dateTimeRange.d.ts.map +0 -1
  125. package/src/components/dateTimeRange.js +0 -17
  126. package/src/components/injectIntl.d.ts +0 -22
  127. package/src/components/injectIntl.d.ts.map +0 -1
  128. package/src/components/injectIntl.js +0 -33
  129. package/src/components/message.d.ts +0 -12
  130. package/src/components/message.d.ts.map +0 -1
  131. package/src/components/message.js +0 -37
  132. package/src/components/plural.d.ts +0 -15
  133. package/src/components/plural.d.ts.map +0 -1
  134. package/src/components/plural.js +0 -29
  135. package/src/components/provider.d.ts +0 -35
  136. package/src/components/provider.d.ts.map +0 -1
  137. package/src/components/provider.js +0 -112
  138. package/src/components/relative.d.ts +0 -12
  139. package/src/components/relative.d.ts.map +0 -1
  140. package/src/components/relative.js +0 -131
  141. package/src/components/useIntl.d.ts +0 -3
  142. package/src/components/useIntl.d.ts.map +0 -1
  143. package/src/components/useIntl.js +0 -12
  144. package/src/types.d.ts +0 -12
  145. package/src/types.d.ts.map +0 -1
  146. package/src/types.js +0 -2
  147. package/src/utils.d.ts +0 -14
  148. package/src/utils.d.ts.map +0 -1
  149. package/src/utils.js +0 -49
@@ -0,0 +1,111 @@
1
+ import * as React from 'react'
2
+ import hoistNonReactStatics from 'hoist-non-react-statics'
3
+ import {invariantIntlContext} from '../utils'
4
+ import {IntlShape} from '../types'
5
+
6
+ function getDisplayName(Component: React.ComponentType<any>): string {
7
+ return Component.displayName || Component.name || 'Component'
8
+ }
9
+
10
+ // TODO: We should provide initial value here
11
+ const IntlContext = React.createContext<IntlShape>(null as any)
12
+ const {Consumer: IntlConsumer, Provider: IntlProvider} = IntlContext
13
+
14
+ export const Provider = IntlProvider
15
+ export const Context = IntlContext
16
+
17
+ export interface Opts<
18
+ IntlPropName extends string = 'intl',
19
+ ForwardRef extends boolean = false
20
+ > {
21
+ intlPropName?: IntlPropName
22
+ forwardRef?: ForwardRef
23
+ enforceContext?: boolean
24
+ }
25
+
26
+ export type WrappedComponentProps<IntlPropName extends string = 'intl'> = {
27
+ [k in IntlPropName]: IntlShape
28
+ }
29
+
30
+ export type WithIntlProps<P> = Omit<P, keyof WrappedComponentProps> & {
31
+ forwardedRef?: React.Ref<any>
32
+ }
33
+
34
+ // TODO: type hoisted static methods.
35
+ // Non ref forwarding overload
36
+ export default function injectIntl<
37
+ IntlPropName extends string = 'intl',
38
+ P extends WrappedComponentProps<IntlPropName> = WrappedComponentProps<any>
39
+ >(
40
+ WrappedComponent: React.ComponentType<P>,
41
+ options?: Opts<IntlPropName, false>
42
+ ): React.FC<WithIntlProps<P>> & {
43
+ WrappedComponent: React.ComponentType<P>
44
+ }
45
+ // Ref forwarding overload.
46
+ export default function injectIntl<
47
+ IntlPropName extends string = 'intl',
48
+ P extends WrappedComponentProps<IntlPropName> = WrappedComponentProps<any>,
49
+ T extends React.ComponentType<P> = any
50
+ >(
51
+ WrappedComponent: React.ComponentType<P>,
52
+ options?: Opts<IntlPropName, true>
53
+ ): React.ForwardRefExoticComponent<
54
+ React.PropsWithoutRef<WithIntlProps<React.PropsWithChildren<P>>> &
55
+ React.RefAttributes<T>
56
+ > & {
57
+ WrappedComponent: React.ComponentType<P>
58
+ }
59
+ export default function injectIntl<
60
+ IntlPropName extends string = 'intl',
61
+ P extends WrappedComponentProps<IntlPropName> = WrappedComponentProps<any>,
62
+ ForwardRef extends boolean = false,
63
+ T extends React.ComponentType<P> = any
64
+ >(
65
+ WrappedComponent: React.ComponentType<P>,
66
+ options?: Opts<IntlPropName, ForwardRef>
67
+ ): React.ForwardRefExoticComponent<
68
+ React.PropsWithoutRef<WithIntlProps<P>> & React.RefAttributes<T>
69
+ > & {
70
+ WrappedComponent: React.ComponentType<P>
71
+ } {
72
+ const {
73
+ intlPropName = 'intl',
74
+ forwardRef = false,
75
+ enforceContext = true,
76
+ } = options || {}
77
+
78
+ const WithIntl: React.FC<P & {forwardedRef?: React.Ref<any>}> & {
79
+ WrappedComponent: React.ComponentType<P>
80
+ } = props => (
81
+ <IntlConsumer>
82
+ {(intl): React.ReactNode => {
83
+ if (enforceContext) {
84
+ invariantIntlContext(intl)
85
+ }
86
+ const intlProp = {[intlPropName]: intl}
87
+
88
+ return (
89
+ <WrappedComponent
90
+ {...props}
91
+ {...intlProp}
92
+ ref={forwardRef ? props.forwardedRef : null}
93
+ />
94
+ )
95
+ }}
96
+ </IntlConsumer>
97
+ )
98
+ WithIntl.displayName = `injectIntl(${getDisplayName(WrappedComponent)})`
99
+ WithIntl.WrappedComponent = WrappedComponent
100
+
101
+ if (forwardRef) {
102
+ return hoistNonReactStatics(
103
+ React.forwardRef<T, P>((props: P, ref) => (
104
+ <WithIntl {...props} forwardedRef={ref} />
105
+ )),
106
+ WrappedComponent
107
+ ) as any
108
+ }
109
+
110
+ return hoistNonReactStatics(WithIntl, WrappedComponent) as any
111
+ }
@@ -0,0 +1,73 @@
1
+ /*
2
+ * Copyright 2015, Yahoo Inc.
3
+ * Copyrights licensed under the New BSD License.
4
+ * See the accompanying LICENSE file for terms.
5
+ */
6
+
7
+ import * as React from 'react'
8
+ import type {
9
+ FormatXMLElementFn,
10
+ Options as IntlMessageFormatOptions,
11
+ PrimitiveType,
12
+ } from 'intl-messageformat'
13
+
14
+ import {MessageDescriptor} from '@formatjs/intl'
15
+ import useIntl from './useIntl'
16
+ import {shallowEqual} from '../utils'
17
+
18
+ export interface Props<
19
+ V extends Record<string, any> = Record<
20
+ string,
21
+ | React.ReactNode
22
+ | PrimitiveType
23
+ | FormatXMLElementFn<React.ReactNode, React.ReactNode>
24
+ >
25
+ > extends MessageDescriptor {
26
+ values?: V
27
+ tagName?: React.ElementType<any>
28
+ children?(nodes: React.ReactNode[]): React.ReactElement | null
29
+ ignoreTag?: IntlMessageFormatOptions['ignoreTag']
30
+ }
31
+
32
+ function areEqual(prevProps: Props, nextProps: Props): boolean {
33
+ const {values, ...otherProps} = prevProps
34
+ const {values: nextValues, ...nextOtherProps} = nextProps
35
+ return (
36
+ shallowEqual(nextValues, values) &&
37
+ shallowEqual(otherProps as any, nextOtherProps)
38
+ )
39
+ }
40
+
41
+ function FormattedMessage(props: Props) {
42
+ const intl = useIntl()
43
+ const {formatMessage, textComponent: Text = React.Fragment} = intl
44
+ const {
45
+ id,
46
+ description,
47
+ defaultMessage,
48
+ values,
49
+ children,
50
+ tagName: Component = Text,
51
+ ignoreTag,
52
+ } = props
53
+
54
+ const descriptor = {id, description, defaultMessage}
55
+ let nodes: React.ReactNode = formatMessage(descriptor, values, {
56
+ ignoreTag,
57
+ })
58
+
59
+ if (typeof children === 'function') {
60
+ return children(Array.isArray(nodes) ? nodes : [nodes])
61
+ }
62
+
63
+ if (Component) {
64
+ return <Component>{React.Children.toArray(nodes)}</Component>
65
+ }
66
+ return <>{nodes}</>
67
+ }
68
+ FormattedMessage.displayName = 'FormattedMessage'
69
+
70
+ const MemoizedFormattedMessage = React.memo<Props>(FormattedMessage, areEqual)
71
+ MemoizedFormattedMessage.displayName = 'MemoizedFormattedMessage'
72
+
73
+ export default MemoizedFormattedMessage
@@ -0,0 +1,45 @@
1
+ /*
2
+ * Copyright 2015, Yahoo Inc.
3
+ * Copyrights licensed under the New BSD License.
4
+ * See the accompanying LICENSE file for terms.
5
+ */
6
+
7
+ import * as React from 'react'
8
+ import {FormatPluralOptions} from '@formatjs/intl'
9
+ import useIntl from './useIntl'
10
+
11
+ interface Props extends FormatPluralOptions {
12
+ value: number
13
+ other: React.ReactNode
14
+ zero?: React.ReactNode
15
+ one?: React.ReactNode
16
+ two?: React.ReactNode
17
+ few?: React.ReactNode
18
+ many?: React.ReactNode
19
+ children?(value: React.ReactNode): React.ReactElement | null
20
+ }
21
+
22
+ const FormattedPlural: React.FC<Props> = props => {
23
+ const {formatPlural, textComponent: Text} = useIntl()
24
+ const {value, other, children} = props
25
+
26
+ const pluralCategory = formatPlural(value, props)
27
+ const formattedPlural = props[pluralCategory as 'one'] || other
28
+
29
+ if (typeof children === 'function') {
30
+ return children(formattedPlural)
31
+ }
32
+ if (Text) {
33
+ return <Text>{formattedPlural}</Text>
34
+ }
35
+ // Work around @types/react where React.FC cannot return string
36
+ return formattedPlural as any
37
+ }
38
+
39
+ FormattedPlural.defaultProps = {
40
+ type: 'cardinal',
41
+ }
42
+
43
+ FormattedPlural.displayName = 'FormattedPlural'
44
+
45
+ export default FormattedPlural
@@ -0,0 +1,196 @@
1
+ /*
2
+ * Copyright 2015, Yahoo Inc.
3
+ * Copyrights licensed under the New BSD License.
4
+ * See the accompanying LICENSE file for terms.
5
+ */
6
+
7
+ import * as React from 'react'
8
+ import {Provider} from './injectIntl'
9
+ import {
10
+ DEFAULT_INTL_CONFIG,
11
+ invariantIntlContext,
12
+ assignUniqueKeysToParts,
13
+ shallowEqual,
14
+ } from '../utils'
15
+ import type {IntlConfig, IntlShape, ResolvedIntlConfig} from '../types'
16
+ import {
17
+ formatMessage as coreFormatMessage,
18
+ IntlCache,
19
+ createIntl as coreCreateIntl,
20
+ CreateIntlFn,
21
+ createIntlCache,
22
+ FormatMessageFn,
23
+ } from '@formatjs/intl'
24
+
25
+ import {
26
+ PrimitiveType,
27
+ FormatXMLElementFn,
28
+ isFormatXMLElementFn,
29
+ } from 'intl-messageformat'
30
+
31
+ interface State {
32
+ /**
33
+ * Explicit intl cache to prevent memory leaks
34
+ */
35
+ cache: IntlCache
36
+ /**
37
+ * Intl object we created
38
+ */
39
+ intl?: IntlShape
40
+ /**
41
+ * list of memoized config we care about.
42
+ * This is important since creating intl is
43
+ * very expensive
44
+ */
45
+ prevConfig: IntlConfig
46
+ }
47
+
48
+ function processIntlConfig<P extends IntlConfig = IntlConfig>(
49
+ config: P
50
+ ): IntlConfig {
51
+ return {
52
+ locale: config.locale,
53
+ timeZone: config.timeZone,
54
+ fallbackOnEmptyString: config.fallbackOnEmptyString,
55
+ formats: config.formats,
56
+ textComponent: config.textComponent,
57
+ messages: config.messages,
58
+ defaultLocale: config.defaultLocale,
59
+ defaultFormats: config.defaultFormats,
60
+ onError: config.onError,
61
+ onWarn: config.onWarn,
62
+ wrapRichTextChunksInFragment: config.wrapRichTextChunksInFragment,
63
+ defaultRichTextElements: config.defaultRichTextElements,
64
+ }
65
+ }
66
+
67
+ function assignUniqueKeysToFormatXMLElementFnArgument<
68
+ T extends Record<
69
+ string,
70
+ | PrimitiveType
71
+ | React.ReactNode
72
+ | FormatXMLElementFn<React.ReactNode, React.ReactNode>
73
+ > = Record<
74
+ string,
75
+ | PrimitiveType
76
+ | React.ReactNode
77
+ | FormatXMLElementFn<React.ReactNode, React.ReactNode>
78
+ >
79
+ >(values?: T): T | undefined {
80
+ if (!values) {
81
+ return values
82
+ }
83
+ return Object.keys(values).reduce((acc: T, k) => {
84
+ const v = values[k]
85
+ ;(acc as any)[k] = isFormatXMLElementFn<React.ReactNode>(v)
86
+ ? assignUniqueKeysToParts(v)
87
+ : v
88
+ return acc
89
+ }, {} as T)
90
+ }
91
+
92
+ const formatMessage: FormatMessageFn<React.ReactNode> = (
93
+ config,
94
+ formatters,
95
+ descriptor,
96
+ rawValues,
97
+ ...rest
98
+ ) => {
99
+ const values = assignUniqueKeysToFormatXMLElementFnArgument(rawValues)
100
+ const chunks = coreFormatMessage(
101
+ config,
102
+ formatters,
103
+ descriptor,
104
+ values as any,
105
+ ...rest
106
+ )
107
+ if (Array.isArray(chunks)) {
108
+ return React.Children.toArray(chunks)
109
+ }
110
+ return chunks as any
111
+ }
112
+
113
+ /**
114
+ * Create intl object
115
+ * @param config intl config
116
+ * @param cache cache for formatter instances to prevent memory leak
117
+ */
118
+ export const createIntl: CreateIntlFn<
119
+ React.ReactNode,
120
+ IntlConfig,
121
+ IntlShape
122
+ > = (
123
+ {defaultRichTextElements: rawDefaultRichTextElements, ...config},
124
+ cache
125
+ ) => {
126
+ const defaultRichTextElements = assignUniqueKeysToFormatXMLElementFnArgument(
127
+ rawDefaultRichTextElements
128
+ )
129
+ const coreIntl = coreCreateIntl<React.ReactNode>(
130
+ {
131
+ ...DEFAULT_INTL_CONFIG,
132
+ ...config,
133
+ defaultRichTextElements,
134
+ },
135
+ cache
136
+ )
137
+
138
+ const resolvedConfig: ResolvedIntlConfig = {
139
+ locale: coreIntl.locale,
140
+ timeZone: coreIntl.timeZone,
141
+ fallbackOnEmptyString: coreIntl.fallbackOnEmptyString,
142
+ formats: coreIntl.formats,
143
+ defaultLocale: coreIntl.defaultLocale,
144
+ defaultFormats: coreIntl.defaultFormats,
145
+ messages: coreIntl.messages,
146
+ onError: coreIntl.onError,
147
+ defaultRichTextElements,
148
+ }
149
+
150
+ return {
151
+ ...coreIntl,
152
+ // @ts-expect-error fix this
153
+ formatMessage: formatMessage.bind(
154
+ null,
155
+ resolvedConfig,
156
+ coreIntl.formatters
157
+ ),
158
+ // @ts-expect-error fix this
159
+ $t: formatMessage.bind(null, resolvedConfig, coreIntl.formatters),
160
+ } as any
161
+ }
162
+
163
+ export default class IntlProvider extends React.PureComponent<
164
+ // Exporting children props so it is composable with other HOCs.
165
+ // See: https://github.com/formatjs/formatjs/issues/1697
166
+ React.PropsWithChildren<IntlConfig>,
167
+ State
168
+ > {
169
+ static displayName = 'IntlProvider'
170
+ static defaultProps = DEFAULT_INTL_CONFIG
171
+ private cache: IntlCache = createIntlCache()
172
+ state: State = {
173
+ cache: this.cache,
174
+ intl: createIntl(processIntlConfig(this.props), this.cache),
175
+ prevConfig: processIntlConfig(this.props),
176
+ }
177
+
178
+ static getDerivedStateFromProps(
179
+ props: Readonly<IntlConfig>,
180
+ {prevConfig, cache}: State
181
+ ): Partial<State> | null {
182
+ const config = processIntlConfig(props)
183
+ if (!shallowEqual(prevConfig, config)) {
184
+ return {
185
+ intl: createIntl(config, cache),
186
+ prevConfig: config,
187
+ }
188
+ }
189
+ return null
190
+ }
191
+
192
+ render(): JSX.Element {
193
+ invariantIntlContext(this.state.intl)
194
+ return <Provider value={this.state.intl}>{this.props.children}</Provider>
195
+ }
196
+ }
@@ -0,0 +1,192 @@
1
+ /*
2
+ * Copyright 2015, Yahoo Inc.
3
+ * Copyrights licensed under the New BSD License.
4
+ * See the accompanying LICENSE file for terms.
5
+ */
6
+ import * as React from 'react'
7
+ import {
8
+ invariant,
9
+ RelativeTimeFormatSingularUnit,
10
+ } from '@formatjs/ecma402-abstract'
11
+ import {FormatRelativeTimeOptions} from '@formatjs/intl'
12
+ import useIntl from './useIntl'
13
+
14
+ const MINUTE = 60
15
+ const HOUR = 60 * 60
16
+ const DAY = 60 * 60 * 24
17
+
18
+ function selectUnit(seconds: number): RelativeTimeFormatSingularUnit {
19
+ const absValue = Math.abs(seconds)
20
+
21
+ if (absValue < MINUTE) {
22
+ return 'second'
23
+ }
24
+
25
+ if (absValue < HOUR) {
26
+ return 'minute'
27
+ }
28
+
29
+ if (absValue < DAY) {
30
+ return 'hour'
31
+ }
32
+
33
+ return 'day'
34
+ }
35
+
36
+ function getDurationInSeconds(unit?: RelativeTimeFormatSingularUnit): number {
37
+ switch (unit) {
38
+ case 'second':
39
+ return 1
40
+ case 'minute':
41
+ return MINUTE
42
+ case 'hour':
43
+ return HOUR
44
+ default:
45
+ return DAY
46
+ }
47
+ }
48
+
49
+ function valueToSeconds(
50
+ value?: number,
51
+ unit?: RelativeTimeFormatSingularUnit
52
+ ): number {
53
+ if (!value) {
54
+ return 0
55
+ }
56
+ switch (unit) {
57
+ case 'second':
58
+ return value
59
+ case 'minute':
60
+ return value * MINUTE
61
+ default:
62
+ return value * HOUR
63
+ }
64
+ }
65
+
66
+ export interface Props extends FormatRelativeTimeOptions {
67
+ value?: number
68
+ unit?: RelativeTimeFormatSingularUnit
69
+ updateIntervalInSeconds?: number
70
+ children?(value: string): React.ReactElement | null
71
+ }
72
+
73
+ const INCREMENTABLE_UNITS: RelativeTimeFormatSingularUnit[] = [
74
+ 'second',
75
+ 'minute',
76
+ 'hour',
77
+ ]
78
+ function canIncrement(
79
+ unit: RelativeTimeFormatSingularUnit = 'second'
80
+ ): boolean {
81
+ return INCREMENTABLE_UNITS.indexOf(unit) > -1
82
+ }
83
+
84
+ const SimpleFormattedRelativeTime: React.FC<
85
+ Omit<Props, 'updateIntervalInSeconds'>
86
+ > = props => {
87
+ const {formatRelativeTime, textComponent: Text} = useIntl()
88
+ const {children, value, unit, ...otherProps} = props
89
+
90
+ const formattedRelativeTime = formatRelativeTime(value || 0, unit, otherProps)
91
+
92
+ if (typeof children === 'function') {
93
+ return children(formattedRelativeTime)
94
+ }
95
+ if (Text) {
96
+ return <Text>{formattedRelativeTime}</Text>
97
+ }
98
+ return <>{formattedRelativeTime}</>
99
+ }
100
+
101
+ const FormattedRelativeTime: React.FC<Props> = ({
102
+ value,
103
+ unit,
104
+ updateIntervalInSeconds,
105
+ ...otherProps
106
+ }) => {
107
+ invariant(
108
+ !updateIntervalInSeconds ||
109
+ !!(updateIntervalInSeconds && canIncrement(unit)),
110
+ 'Cannot schedule update with unit longer than hour'
111
+ )
112
+ const [prevUnit, setPrevUnit] = React.useState<
113
+ RelativeTimeFormatSingularUnit | undefined
114
+ >()
115
+ const [prevValue, setPrevValue] = React.useState<number>(0)
116
+ const [currentValueInSeconds, setCurrentValueInSeconds] =
117
+ React.useState<number>(0)
118
+ let updateTimer: number
119
+
120
+ if (unit !== prevUnit || value !== prevValue) {
121
+ setPrevValue(value || 0)
122
+ setPrevUnit(unit)
123
+ setCurrentValueInSeconds(
124
+ canIncrement(unit) ? valueToSeconds(value, unit) : 0
125
+ )
126
+ }
127
+
128
+ React.useEffect(() => {
129
+ function clearUpdateTimer() {
130
+ clearTimeout(updateTimer)
131
+ }
132
+ clearUpdateTimer()
133
+ // If there's no interval and we cannot increment this unit, do nothing
134
+ if (!updateIntervalInSeconds || !canIncrement(unit)) {
135
+ return clearUpdateTimer
136
+ }
137
+ // Figure out the next interesting time
138
+ const nextValueInSeconds = currentValueInSeconds - updateIntervalInSeconds
139
+ const nextUnit = selectUnit(nextValueInSeconds)
140
+ // We've reached the max auto incrementable unit, don't schedule another update
141
+ if (nextUnit === 'day') {
142
+ return clearUpdateTimer
143
+ }
144
+
145
+ const unitDuration = getDurationInSeconds(nextUnit)
146
+ const remainder = nextValueInSeconds % unitDuration
147
+ const prevInterestingValueInSeconds = nextValueInSeconds - remainder
148
+ const nextInterestingValueInSeconds =
149
+ prevInterestingValueInSeconds >= currentValueInSeconds
150
+ ? prevInterestingValueInSeconds - unitDuration
151
+ : prevInterestingValueInSeconds
152
+ const delayInSeconds = Math.abs(
153
+ nextInterestingValueInSeconds - currentValueInSeconds
154
+ )
155
+
156
+ if (currentValueInSeconds !== nextInterestingValueInSeconds) {
157
+ updateTimer = setTimeout(
158
+ () => setCurrentValueInSeconds(nextInterestingValueInSeconds),
159
+ delayInSeconds * 1e3
160
+ ) as unknown as number
161
+ }
162
+ return clearUpdateTimer
163
+ }, [currentValueInSeconds, updateIntervalInSeconds, unit])
164
+
165
+ let currentValue = value || 0
166
+ let currentUnit = unit
167
+
168
+ if (
169
+ canIncrement(unit) &&
170
+ typeof currentValueInSeconds === 'number' &&
171
+ updateIntervalInSeconds
172
+ ) {
173
+ currentUnit = selectUnit(currentValueInSeconds)
174
+ const unitDuration = getDurationInSeconds(currentUnit)
175
+ currentValue = Math.round(currentValueInSeconds / unitDuration)
176
+ }
177
+ return (
178
+ <SimpleFormattedRelativeTime
179
+ value={currentValue}
180
+ unit={currentUnit}
181
+ {...otherProps}
182
+ />
183
+ )
184
+ }
185
+
186
+ FormattedRelativeTime.displayName = 'FormattedRelativeTime'
187
+ FormattedRelativeTime.defaultProps = {
188
+ value: 0,
189
+ unit: 'second',
190
+ }
191
+
192
+ export default FormattedRelativeTime
@@ -0,0 +1,10 @@
1
+ import * as React from 'react'
2
+ import {Context} from './injectIntl'
3
+ import {invariantIntlContext} from '../utils'
4
+ import {IntlShape} from '../types'
5
+
6
+ export default function useIntl(): IntlShape {
7
+ const intl = React.useContext(Context)
8
+ invariantIntlContext(intl)
9
+ return intl
10
+ }
package/src/types.ts ADDED
@@ -0,0 +1,29 @@
1
+ /*
2
+ * Copyright 2015, Yahoo Inc.
3
+ * Copyrights licensed under the New BSD License.
4
+ * See the accompanying LICENSE file for terms.
5
+ */
6
+ import * as React from 'react'
7
+ import {
8
+ ResolvedIntlConfig as CoreResolvedIntlConfig,
9
+ IntlFormatters,
10
+ Formatters,
11
+ } from '@formatjs/intl'
12
+ import {DEFAULT_INTL_CONFIG} from './utils'
13
+ export type IntlConfig = Omit<
14
+ ResolvedIntlConfig,
15
+ keyof typeof DEFAULT_INTL_CONFIG
16
+ > &
17
+ Partial<typeof DEFAULT_INTL_CONFIG>
18
+
19
+ export interface ResolvedIntlConfig
20
+ extends CoreResolvedIntlConfig<React.ReactNode> {
21
+ textComponent?: React.ComponentType | keyof React.ReactHTML
22
+ wrapRichTextChunksInFragment?: boolean
23
+ }
24
+
25
+ export interface IntlShape
26
+ extends ResolvedIntlConfig,
27
+ IntlFormatters<React.ReactNode> {
28
+ formatters: Formatters
29
+ }