@edgeandnode/eds-react 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -8
- package/dist/EDSContext.d.ts +4 -1
- package/dist/EDSContext.d.ts.map +1 -1
- package/dist/EDSContext.js +7 -0
- package/dist/EDSContext.js.map +1 -1
- package/dist/EDSProvider.d.ts +13 -6
- package/dist/EDSProvider.d.ts.map +1 -1
- package/dist/EDSProvider.js +60 -68
- package/dist/EDSProvider.js.map +1 -1
- package/dist/components/Address.d.ts +3 -3
- package/dist/components/Address.d.ts.map +1 -1
- package/dist/components/Address.js.map +1 -1
- package/dist/components/Avatar.d.ts +3 -3
- package/dist/components/Avatar.d.ts.map +1 -1
- package/dist/components/Avatar.js +3 -2
- package/dist/components/Avatar.js.map +1 -1
- package/dist/components/Breadcrumbs.parts.d.ts +7 -6
- package/dist/components/Breadcrumbs.parts.d.ts.map +1 -1
- package/dist/components/Breadcrumbs.parts.js +15 -14
- package/dist/components/Breadcrumbs.parts.js.map +1 -1
- package/dist/components/Button.d.ts.map +1 -1
- package/dist/components/Button.js +3 -2
- package/dist/components/Button.js.map +1 -1
- package/dist/components/Card.d.ts +8 -4
- package/dist/components/Card.d.ts.map +1 -1
- package/dist/components/Card.js +2 -1
- package/dist/components/Card.js.map +1 -1
- package/dist/components/Cluster.d.ts +3 -3
- package/dist/components/Cluster.d.ts.map +1 -1
- package/dist/components/Cluster.js.map +1 -1
- package/dist/components/CodeBlock.parts.d.ts.map +1 -1
- package/dist/components/CodeBlock.parts.js +16 -20
- package/dist/components/CodeBlock.parts.js.map +1 -1
- package/dist/components/CopyButton.d.ts.map +1 -1
- package/dist/components/CopyButton.js +3 -2
- package/dist/components/CopyButton.js.map +1 -1
- package/dist/components/Icon.d.ts.map +1 -1
- package/dist/components/Icon.js +3 -3
- package/dist/components/Icon.js.map +1 -1
- package/dist/components/Keyboard.js +1 -1
- package/dist/components/LocalTheme.d.ts +29 -0
- package/dist/components/LocalTheme.d.ts.map +1 -0
- package/dist/components/LocalTheme.js +28 -0
- package/dist/components/LocalTheme.js.map +1 -0
- package/dist/components/Menu.parts.d.ts +43 -9
- package/dist/components/Menu.parts.d.ts.map +1 -1
- package/dist/components/Menu.parts.js +121 -115
- package/dist/components/Menu.parts.js.map +1 -1
- package/dist/components/Modal.parts.d.ts +1 -1
- package/dist/components/Modal.parts.d.ts.map +1 -1
- package/dist/components/Modal.parts.js +22 -11
- package/dist/components/Modal.parts.js.map +1 -1
- package/dist/components/Pane.parts.d.ts +23 -22
- package/dist/components/Pane.parts.d.ts.map +1 -1
- package/dist/components/Pane.parts.js +3 -3
- package/dist/components/Pane.parts.js.map +1 -1
- package/dist/components/Prose.d.ts +1 -1
- package/dist/components/Prose.d.ts.map +1 -1
- package/dist/components/Prose.js +3 -3
- package/dist/components/Prose.js.map +1 -1
- package/dist/components/Prose.meta.d.ts +5 -0
- package/dist/components/Prose.meta.d.ts.map +1 -1
- package/dist/components/Prose.meta.js +10 -0
- package/dist/components/Prose.meta.js.map +1 -1
- package/dist/components/SegmentedControl.parts.d.ts.map +1 -1
- package/dist/components/SegmentedControl.parts.js +3 -2
- package/dist/components/SegmentedControl.parts.js.map +1 -1
- package/dist/components/Select.parts.d.ts +12 -7
- package/dist/components/Select.parts.d.ts.map +1 -1
- package/dist/components/Select.parts.js +28 -18
- package/dist/components/Select.parts.js.map +1 -1
- package/dist/components/TabSet.parts.d.ts.map +1 -1
- package/dist/components/TabSet.parts.js +3 -2
- package/dist/components/TabSet.parts.js.map +1 -1
- package/dist/components/Table.parts.d.ts.map +1 -1
- package/dist/components/Table.parts.js +3 -4
- package/dist/components/Table.parts.js.map +1 -1
- package/dist/components/ThemeSwitcher.d.ts +5 -5
- package/dist/components/ThemeSwitcher.d.ts.map +1 -1
- package/dist/components/ThemeSwitcher.js +6 -5
- package/dist/components/ThemeSwitcher.js.map +1 -1
- package/dist/components/Tooltip.parts.js +2 -2
- package/dist/components/Tooltip.parts.js.map +1 -1
- package/dist/components/base/Addon.d.ts.map +1 -1
- package/dist/components/base/Addon.js +2 -4
- package/dist/components/base/Addon.js.map +1 -1
- package/dist/components/base/ButtonOrLink.parts.d.ts.map +1 -1
- package/dist/components/base/ButtonOrLink.parts.js +2 -1
- package/dist/components/base/ButtonOrLink.parts.js.map +1 -1
- package/dist/components/base/MaybeButtonOrLink.d.ts +7 -5
- package/dist/components/base/MaybeButtonOrLink.d.ts.map +1 -1
- package/dist/components/base/MaybeButtonOrLink.js +7 -2
- package/dist/components/base/MaybeButtonOrLink.js.map +1 -1
- package/dist/components/base/Render.d.ts +25 -6
- package/dist/components/base/Render.d.ts.map +1 -1
- package/dist/components/base/Render.js +4 -3
- package/dist/components/base/Render.js.map +1 -1
- package/dist/components/base/index.d.ts +1 -1
- package/dist/components/base/index.d.ts.map +1 -1
- package/dist/components/base/index.js.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/hooks/useCSSProp.js +1 -1
- package/dist/hooks/useCSSProp.js.map +1 -1
- package/dist/hooks/useEDS.d.ts +24 -1
- package/dist/hooks/useEDS.d.ts.map +1 -1
- package/dist/hooks/useEDS.js +26 -2
- package/dist/hooks/useEDS.js.map +1 -1
- package/dist/hooks/useStyleObserver.d.ts.map +1 -1
- package/dist/hooks/useStyleObserver.js +2 -1
- package/dist/hooks/useStyleObserver.js.map +1 -1
- package/dist/icons/CalendarDynamicIcon.d.ts.map +1 -1
- package/dist/icons/CalendarDynamicIcon.js +3 -1
- package/dist/icons/CalendarDynamicIcon.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/theme-persistence.d.ts +18 -0
- package/dist/theme-persistence.d.ts.map +1 -0
- package/dist/theme-persistence.js +25 -0
- package/dist/theme-persistence.js.map +1 -0
- package/dist/utils/getReactNodeKey.d.ts.map +1 -1
- package/dist/utils/getReactNodeKey.js +31 -13
- package/dist/utils/getReactNodeKey.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/internal/getTooltipProps.d.ts +9 -0
- package/dist/utils/internal/getTooltipProps.d.ts.map +1 -0
- package/dist/utils/internal/getTooltipProps.js +18 -0
- package/dist/utils/internal/getTooltipProps.js.map +1 -0
- package/dist/utils/internal/index.d.ts +4 -0
- package/dist/utils/internal/index.d.ts.map +1 -0
- package/dist/utils/internal/index.js +4 -0
- package/dist/utils/internal/index.js.map +1 -0
- package/dist/utils/internal/resolveLazy.d.ts +29 -0
- package/dist/utils/internal/resolveLazy.d.ts.map +1 -0
- package/dist/utils/internal/resolveLazy.js +40 -0
- package/dist/utils/internal/resolveLazy.js.map +1 -0
- package/dist/utils/internal/styleScheduler.d.ts.map +1 -0
- package/dist/utils/internal/styleScheduler.js.map +1 -0
- package/dist/utils/isComponentType.d.ts +9 -0
- package/dist/utils/isComponentType.d.ts.map +1 -0
- package/dist/utils/isComponentType.js +22 -0
- package/dist/utils/isComponentType.js.map +1 -0
- package/dist/utils/isReactNode.d.ts +7 -0
- package/dist/utils/isReactNode.d.ts.map +1 -1
- package/dist/utils/isReactNode.js +11 -0
- package/dist/utils/isReactNode.js.map +1 -1
- package/dist/utils/reactNodeToString.d.ts +3 -0
- package/dist/utils/reactNodeToString.d.ts.map +1 -1
- package/dist/utils/reactNodeToString.js +6 -0
- package/dist/utils/reactNodeToString.js.map +1 -1
- package/dist/utils/styleObserver.js +1 -1
- package/dist/utils/trimReactNode.d.ts +3 -0
- package/dist/utils/trimReactNode.d.ts.map +1 -1
- package/dist/utils/trimReactNode.js +6 -0
- package/dist/utils/trimReactNode.js.map +1 -1
- package/package.json +24 -23
- package/scripts/update-skill.cjs +18 -5
- package/skill/SKILL.md +95 -57
- package/skill/references/tokens.md +2 -8
- package/src/EDSContext.ts +10 -1
- package/src/EDSProvider.tsx +111 -80
- package/src/components/Address.tsx +4 -3
- package/src/components/Avatar.tsx +10 -5
- package/src/components/Breadcrumbs.parts.tsx +35 -18
- package/src/components/Button.tsx +7 -2
- package/src/components/Card.tsx +14 -8
- package/src/components/Cluster.tsx +5 -3
- package/src/components/CodeBlock.parts.tsx +20 -24
- package/src/components/CopyButton.tsx +3 -2
- package/src/components/Icon.tsx +3 -9
- package/src/components/Keyboard.tsx +1 -1
- package/src/components/LocalTheme.tsx +47 -0
- package/src/components/Menu.parts.tsx +368 -245
- package/src/components/Modal.parts.tsx +28 -11
- package/src/components/Pane.parts.tsx +31 -26
- package/src/components/Prose.meta.ts +10 -0
- package/src/components/Prose.tsx +4 -3
- package/src/components/SegmentedControl.parts.tsx +3 -8
- package/src/components/Select.parts.tsx +43 -34
- package/src/components/TabSet.parts.tsx +3 -5
- package/src/components/Table.parts.tsx +3 -3
- package/src/components/ThemeSwitcher.tsx +13 -15
- package/src/components/Tooltip.parts.tsx +2 -2
- package/src/components/base/Addon.tsx +3 -4
- package/src/components/base/ButtonOrLink.parts.tsx +2 -1
- package/src/components/base/MaybeButtonOrLink.tsx +11 -6
- package/src/components/base/Render.tsx +28 -4
- package/src/components/base/index.ts +2 -0
- package/src/components/index.ts +8 -0
- package/src/hooks/useCSSProp.ts +1 -1
- package/src/hooks/useEDS.ts +26 -2
- package/src/hooks/useStyleObserver.ts +2 -1
- package/src/icons/CalendarDynamicIcon.tsx +1 -0
- package/src/index.ts +2 -1
- package/src/theme-persistence.ts +31 -0
- package/src/utils/getReactNodeKey.ts +30 -14
- package/src/utils/index.ts +1 -1
- package/src/utils/internal/getTooltipProps.ts +25 -0
- package/src/utils/internal/index.ts +3 -0
- package/src/utils/internal/resolveLazy.ts +44 -0
- package/src/utils/isComponentType.ts +27 -0
- package/src/utils/isReactNode.ts +11 -0
- package/src/utils/reactNodeToString.ts +7 -0
- package/src/utils/styleObserver.ts +1 -1
- package/src/utils/trimReactNode.tsx +7 -0
- package/dist/utils/styleScheduler.d.ts.map +0 -1
- package/dist/utils/styleScheduler.js.map +0 -1
- package/src/__snapshots__/compiled.css +0 -22499
- package/styles/tailwind.storybook.css +0 -19
- /package/dist/utils/{styleScheduler.d.ts → internal/styleScheduler.d.ts} +0 -0
- /package/dist/utils/{styleScheduler.js → internal/styleScheduler.js} +0 -0
- /package/src/utils/{styleScheduler.ts → internal/styleScheduler.ts} +0 -0
|
@@ -30,22 +30,46 @@ export type RenderProp<State extends RenderFnState = undefined, ElementProps = u
|
|
|
30
30
|
| ReactElement<ElementProps>
|
|
31
31
|
| RenderFn<State, ElementProps>
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Props of a generic HTML element, for components whose element can be changed with `as` or
|
|
35
|
+
* `render`.
|
|
36
|
+
*/
|
|
37
|
+
export interface HTMLElementProps extends HTMLProps<HTMLElement> {}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Props to change the element a component renders: a tag name with `as`, or an element or render
|
|
41
|
+
* function with `render`, which is also how to pass props specific to that element (e.g.
|
|
42
|
+
* `render={<a href="/" />}`).
|
|
43
|
+
*/
|
|
44
|
+
export type AsOrRenderProps<State extends RenderFnState = undefined> =
|
|
45
|
+
| {
|
|
46
|
+
/** Tag name of the element to render. */
|
|
47
|
+
as?: keyof HTMLElementTagNameMap | undefined
|
|
48
|
+
render?: undefined
|
|
49
|
+
}
|
|
50
|
+
| {
|
|
51
|
+
as?: undefined
|
|
52
|
+
/** Element or render function to render instead of the default element. */
|
|
53
|
+
render?: RenderProp<State> | undefined
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type RenderProps<State extends RenderFnState = undefined> = AsOrRenderProps<State> & {
|
|
35
57
|
state?: State | undefined
|
|
36
58
|
[key: string]: unknown
|
|
37
59
|
}
|
|
38
60
|
|
|
39
61
|
/**
|
|
40
|
-
* A utility component that provides polymorphic rendering capabilities. It
|
|
41
|
-
* element or
|
|
62
|
+
* A utility component that provides polymorphic rendering capabilities. It renders a `div` by
|
|
63
|
+
* default, the tag name passed in `as`, or the element or render function passed in `render`.
|
|
42
64
|
*/
|
|
43
65
|
export function Render<State extends RenderFnState>({
|
|
66
|
+
as,
|
|
44
67
|
render,
|
|
45
68
|
state,
|
|
46
69
|
...props
|
|
47
70
|
}: RenderProps<State>) {
|
|
48
71
|
return useRender({
|
|
72
|
+
defaultTagName: as,
|
|
49
73
|
render,
|
|
50
74
|
state: state !== undefined ? state : undefined,
|
|
51
75
|
props,
|
package/src/components/index.ts
CHANGED
|
@@ -44,6 +44,14 @@ export { Label, type LabelProps } from './Label.tsx'
|
|
|
44
44
|
export { LabelMeta } from './Label.meta.ts'
|
|
45
45
|
export { Link, type LinkProps } from './Link.tsx'
|
|
46
46
|
export { LinkMeta } from './Link.meta.ts'
|
|
47
|
+
export {
|
|
48
|
+
Dark,
|
|
49
|
+
Light,
|
|
50
|
+
LocalTheme,
|
|
51
|
+
type DarkProps,
|
|
52
|
+
type LightProps,
|
|
53
|
+
type LocalThemeProps,
|
|
54
|
+
} from './LocalTheme.tsx'
|
|
47
55
|
export {
|
|
48
56
|
Menu,
|
|
49
57
|
type MenuEmptyProps,
|
package/src/hooks/useCSSProp.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type EDSComponent,
|
|
9
9
|
} from '@edgeandnode/eds-css'
|
|
10
10
|
|
|
11
|
-
import { styleScheduler } from '../utils/index.ts'
|
|
11
|
+
import { styleScheduler } from '../utils/internal/index.ts'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Returns a `ref` to set on an element, and an async function to get the current value of a CSS
|
package/src/hooks/useEDS.ts
CHANGED
|
@@ -1,19 +1,43 @@
|
|
|
1
1
|
import { useContext } from 'react'
|
|
2
2
|
|
|
3
|
-
import { EDSContext } from '../EDSContext.ts'
|
|
3
|
+
import { EDSContext, LocalThemeContext, THEME_CLASSES } from '../EDSContext.ts'
|
|
4
4
|
|
|
5
5
|
/** Throws an error if used outside of a `EDSProvider`. */
|
|
6
6
|
export function useEDS() {
|
|
7
7
|
const context = useContext(EDSContext)
|
|
8
|
+
const localTheme = useContext(LocalThemeContext)
|
|
8
9
|
|
|
9
10
|
if (context === null) {
|
|
10
11
|
throw new Error('EDS components must be wrapped in a `EDSProvider`')
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
return {
|
|
15
|
+
/**
|
|
16
|
+
* The page's theme, as set on `EDSProvider` or with `setTheme`: `'dark'`, `'light'`, or
|
|
17
|
+
* `'system'` (follows `systemTheme`).
|
|
18
|
+
*/
|
|
14
19
|
theme: context.theme,
|
|
15
|
-
|
|
20
|
+
/** Changes the page's theme (use `ThemeSwitcher` for the UI) */
|
|
16
21
|
setTheme: context.setTheme,
|
|
22
|
+
/** The theme the OS prefers (`prefers-color-scheme`), which the `'system'` theme follows. */
|
|
23
|
+
systemTheme: context.systemTheme,
|
|
24
|
+
/**
|
|
25
|
+
* The theme of the nearest `Dark`, `Light`, or `LocalTheme` ancestor, or `undefined` if there's
|
|
26
|
+
* none.
|
|
27
|
+
*/
|
|
28
|
+
localTheme,
|
|
29
|
+
/**
|
|
30
|
+
* The theme content is rendered in: `localTheme`, or the page's theme with `'system'` resolved
|
|
31
|
+
* to `systemTheme`.
|
|
32
|
+
*/
|
|
33
|
+
activeTheme: localTheme ?? (context.theme === 'system' ? context.systemTheme : context.theme),
|
|
34
|
+
/**
|
|
35
|
+
* The class of the nearest `Dark`, `Light`, or `LocalTheme` ancestor, or `undefined` if there's
|
|
36
|
+
* none. Add it to content rendered outside of that ancestor in the DOM (e.g. with
|
|
37
|
+
* `createPortal`) so it keeps its theme.
|
|
38
|
+
*/
|
|
39
|
+
localThemeClass: localTheme !== undefined ? THEME_CLASSES[localTheme] : undefined,
|
|
40
|
+
/** The `dir` and `lang` of `EDSProvider`, to spread on content rendered outside of it. */
|
|
17
41
|
dirProps: {
|
|
18
42
|
dir: context.direction,
|
|
19
43
|
lang: context.language,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useCallback, useLayoutEffect, useRef, type Ref } from 'react'
|
|
2
2
|
import { useMergedRefs } from '@base-ui/utils/useMergedRefs'
|
|
3
3
|
|
|
4
|
-
import { styleObserver,
|
|
4
|
+
import { styleObserver, type StyleObserverRecord } from '../utils/index.ts'
|
|
5
|
+
import { styleScheduler } from '../utils/internal/index.ts'
|
|
5
6
|
import { useEffectWithRefDeps } from './useEffectWithRefDeps.ts'
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -16,6 +16,7 @@ export interface CalendarDynamicIconProps extends Omit<IconProps, 'alt'> {
|
|
|
16
16
|
|
|
17
17
|
export function CalendarDynamicIcon({
|
|
18
18
|
alt,
|
|
19
|
+
// oxlint-disable-next-line react/purity -- `useState(() => new Date())` would need `'use client'`, which would cause hydration mismatches (time zone, locale) where this is rendered by a server component
|
|
19
20
|
date = new Date(),
|
|
20
21
|
...props
|
|
21
22
|
}: CalendarDynamicIconProps) {
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Not a client module, so server components can call `getThemeFromCookies`
|
|
2
|
+
|
|
3
|
+
import type { Theme } from './EDSContext.ts'
|
|
4
|
+
|
|
5
|
+
export const PERSISTENCE_KEY = 'eds-active-theme'
|
|
6
|
+
export const COOKIE_REGEX = new RegExp(`(?:^|;\\s*)${PERSISTENCE_KEY}=([^;]+)`)
|
|
7
|
+
|
|
8
|
+
export const VALID_THEMES: readonly Theme[] = ['dark', 'light', 'system']
|
|
9
|
+
|
|
10
|
+
export function isValidTheme(value: unknown): value is Theme {
|
|
11
|
+
return typeof value === 'string' && (VALID_THEMES as readonly string[]).includes(value)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function parseThemeCookie(cookies: string | null) {
|
|
15
|
+
const value = cookies?.match(COOKIE_REGEX)?.[1]
|
|
16
|
+
return isValidTheme(value) ? value : undefined
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns the theme saved by `EDSProvider`'s `persistTheme="cookie"` from the request's headers, or
|
|
21
|
+
* `undefined` if there's none. Pass the result to `defaultTheme`, so the server renders the saved
|
|
22
|
+
* theme too.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ````tsx
|
|
26
|
+
* <EDSProvider persistTheme="cookie" defaultTheme={getThemeFromCookies(await headers())}>
|
|
27
|
+
* ````
|
|
28
|
+
*/
|
|
29
|
+
export function getThemeFromCookies(headers: Pick<Headers, 'get'>) {
|
|
30
|
+
return parseThemeCookie(headers.get('cookie'))
|
|
31
|
+
}
|
|
@@ -2,6 +2,7 @@ import { Children, isValidElement, type ReactNode } from 'react'
|
|
|
2
2
|
|
|
3
3
|
import { getKey } from '@edgeandnode/eds-utils'
|
|
4
4
|
|
|
5
|
+
import { resolveLazy } from './internal/index.ts'
|
|
5
6
|
import { reactNodeToString } from './reactNodeToString.ts'
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -12,18 +13,33 @@ import { reactNodeToString } from './reactNodeToString.ts'
|
|
|
12
13
|
* @returns A string key that uniquely identifies the node structure and content.
|
|
13
14
|
*/
|
|
14
15
|
export function getReactNodeKey(reactNode: ReactNode): string {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
try {
|
|
17
|
+
const keys =
|
|
18
|
+
Children.map(reactNode, (child) => {
|
|
19
|
+
if (!child || typeof child === 'boolean') {
|
|
20
|
+
return 'empty'
|
|
21
|
+
}
|
|
22
|
+
if (!isValidElement(child)) {
|
|
23
|
+
return getKey(child)
|
|
24
|
+
}
|
|
25
|
+
if (child.key !== null) {
|
|
26
|
+
return `key:${child.key}`
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Key the type by the component it resolves to, so that identical content created by a
|
|
30
|
+
* server component gets the same key in every server response (each of which has new lazy
|
|
31
|
+
* types)
|
|
32
|
+
*/
|
|
33
|
+
return `${getKey(resolveLazy(child.type))}:${reactNodeToString(child)}`
|
|
34
|
+
}) ?? []
|
|
35
|
+
return keys.length > 0 ? keys.join('|') : 'empty'
|
|
36
|
+
} catch (error) {
|
|
37
|
+
/**
|
|
38
|
+
* Resolving a lazy value (e.g. from a server component) suspends (throws a thenable) while it's
|
|
39
|
+
* still loading, which isn't possible in effects, where this is also called. Fall back to the
|
|
40
|
+
* node's identity then, but let any other error through.
|
|
41
|
+
*/
|
|
42
|
+
if (typeof error !== 'object' || error === null || !('then' in error)) throw error
|
|
43
|
+
return getKey(reactNode)
|
|
44
|
+
}
|
|
29
45
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -3,12 +3,12 @@ export { getAssetUrl } from './getAssetUrl.ts'
|
|
|
3
3
|
export { getCSSPropsAttributes } from './getCSSPropsAttributes.ts'
|
|
4
4
|
export { getReactNodeKey } from './getReactNodeKey.ts'
|
|
5
5
|
export { InlineCounter } from './InlineCounter.tsx'
|
|
6
|
+
export { isComponentType } from './isComponentType.ts'
|
|
6
7
|
export { isReactNode } from './isReactNode.ts'
|
|
7
8
|
export { reactNodeToString } from './reactNodeToString.ts'
|
|
8
9
|
export { RenderCount } from './RenderCount.tsx'
|
|
9
10
|
export { setInputValue } from './setInputValue.ts'
|
|
10
11
|
export { splitProps } from './splitProps.ts'
|
|
11
12
|
export { styleObserver, type StyleObserverRecord } from './styleObserver.ts'
|
|
12
|
-
export { styleScheduler } from './styleScheduler.ts'
|
|
13
13
|
export { trimReactNode } from './trimReactNode.tsx'
|
|
14
14
|
export type { OptionValue, Stringifiable } from './types.ts'
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TooltipProps } from '../../components/Tooltip.tsx'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalizes a component's `tooltip` prop, which accepts either the tooltip's content or its props.
|
|
5
|
+
* Checks for props (a plain object that isn't a React node) rather than for content, so that any
|
|
6
|
+
* kind of node is treated as content, including ones that `isReactNode` doesn't recognize (e.g.
|
|
7
|
+
* portals or promises).
|
|
8
|
+
*/
|
|
9
|
+
export function getTooltipProps(
|
|
10
|
+
tooltip: TooltipProps['content'] | Omit<TooltipProps, 'children'>,
|
|
11
|
+
): Omit<TooltipProps, 'children'> {
|
|
12
|
+
if (tooltip === undefined) return {}
|
|
13
|
+
return isTooltipProps(tooltip) ? tooltip : { content: tooltip }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isTooltipProps(
|
|
17
|
+
tooltip: Exclude<TooltipProps['content'], undefined> | Omit<TooltipProps, 'children'>,
|
|
18
|
+
): tooltip is Omit<TooltipProps, 'children'> {
|
|
19
|
+
return (
|
|
20
|
+
typeof tooltip === 'object' &&
|
|
21
|
+
tooltip !== null &&
|
|
22
|
+
!('$$typeof' in tooltip) &&
|
|
23
|
+
[Object.prototype, null].includes(Object.getPrototypeOf(tooltip))
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Lazy } from 'react-is'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether `value` is a lazy value, which is how the React Server Components payload can encode an
|
|
5
|
+
* element passed as a prop from a server component (e.g. when it's rendered by an async server
|
|
6
|
+
* component), or the `type` of an element created by a server component (so the client can suspend
|
|
7
|
+
* on that component's code). `React.lazy()` components are lazy values too.
|
|
8
|
+
*
|
|
9
|
+
* Not to be confused with `react-is`'s `isLazy`, which checks whether an element's type is lazy.
|
|
10
|
+
*/
|
|
11
|
+
export function isLazy(
|
|
12
|
+
value: unknown,
|
|
13
|
+
): value is { _payload: unknown; _init: (payload: unknown) => unknown } {
|
|
14
|
+
return (
|
|
15
|
+
typeof value === 'object' &&
|
|
16
|
+
value !== null &&
|
|
17
|
+
'$$typeof' in value &&
|
|
18
|
+
value.$$typeof === Lazy &&
|
|
19
|
+
'_payload' in value &&
|
|
20
|
+
'_init' in value &&
|
|
21
|
+
typeof value._init === 'function'
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Resolves a lazy value (see `isLazy`) to what it stands for. React resolves lazy values when
|
|
27
|
+
* rendering them, but code that inspects nodes or element types (`isValidElement`, `cloneElement`,
|
|
28
|
+
* reading `props`, comparing `type`, etc.) must resolve them first. Suspends (throws) while the
|
|
29
|
+
* value's content or code is still loading, so it must be called during render.
|
|
30
|
+
*
|
|
31
|
+
* React has no public API for this. Its `Children` utilities resolve lazy values but only keep
|
|
32
|
+
* renderable results, so they drop a function component and throw on a `memo` or `forwardRef` one.
|
|
33
|
+
* This relies on the same internal `_init` and `_payload` fields they use, which is why it's kept
|
|
34
|
+
* internal rather than offered as public API. Returns other values as is, including lazy ones in an
|
|
35
|
+
* unexpected shape.
|
|
36
|
+
*
|
|
37
|
+
* The return type is the same as the argument's because TypeScript already types a lazy value as
|
|
38
|
+
* what it resolves to.
|
|
39
|
+
*/
|
|
40
|
+
export function resolveLazy<Value>(value: Value): Value {
|
|
41
|
+
let resolvedValue: unknown = value
|
|
42
|
+
while (isLazy(resolvedValue)) resolvedValue = resolvedValue._init(resolvedValue._payload)
|
|
43
|
+
return resolvedValue as Value
|
|
44
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from 'react'
|
|
2
|
+
import { ForwardRef, Memo } from 'react-is'
|
|
3
|
+
|
|
4
|
+
import { isLazy, resolveLazy } from './internal/index.ts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Whether `value` is a component (function, class, `forwardRef`, or `memo`) rather than a node.
|
|
8
|
+
*
|
|
9
|
+
* Like React's `Children` utilities, this resolves lazy values (e.g. elements from server
|
|
10
|
+
* components), which suspends while one is still loading, so call it during render.
|
|
11
|
+
*/
|
|
12
|
+
export function isComponentType<Props>(
|
|
13
|
+
value: ReactNode | ComponentType<Props>,
|
|
14
|
+
): value is ComponentType<Props> {
|
|
15
|
+
/**
|
|
16
|
+
* Unlike `react-is`'s `isValidElementType`, which returns `true` for any lazy value, resolving
|
|
17
|
+
* tells lazy components (e.g. `React.lazy()`) apart from lazy nodes.
|
|
18
|
+
*/
|
|
19
|
+
if (isLazy(value)) return isComponentType(resolveLazy(value))
|
|
20
|
+
return (
|
|
21
|
+
typeof value === 'function' ||
|
|
22
|
+
(typeof value === 'object' &&
|
|
23
|
+
value !== null &&
|
|
24
|
+
'$$typeof' in value &&
|
|
25
|
+
(value.$$typeof === ForwardRef || value.$$typeof === Memo))
|
|
26
|
+
)
|
|
27
|
+
}
|
package/src/utils/isReactNode.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { isValidElement, type ReactNode } from 'react'
|
|
2
2
|
|
|
3
|
+
import { isLazy, resolveLazy } from './internal/index.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Whether `value` is a React node: `null`, `undefined`, a string, number, or boolean, an element, a
|
|
7
|
+
* lazy node (e.g. an element from a server component), or an array of these.
|
|
8
|
+
*
|
|
9
|
+
* Like React's `Children` utilities, this resolves lazy values (e.g. elements from server
|
|
10
|
+
* components), which suspends while one is still loading, so call it during render.
|
|
11
|
+
*/
|
|
3
12
|
export function isReactNode(value: unknown): value is ReactNode {
|
|
13
|
+
// Resolving tells lazy nodes apart from lazy components (e.g. `React.lazy()`)
|
|
14
|
+
if (isLazy(value)) return isReactNode(resolveLazy(value))
|
|
4
15
|
return (
|
|
5
16
|
value === null ||
|
|
6
17
|
value === undefined ||
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { isValidElement, type ReactElement, type ReactNode } from 'react'
|
|
2
2
|
|
|
3
|
+
import { isLazy, resolveLazy } from './internal/index.ts'
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Converts a `ReactNode` to a `string` representation. Extracts text content from React elements
|
|
5
7
|
* recursively.
|
|
6
8
|
*
|
|
7
9
|
* Based on `https://github.com/sunknudsen/react-node-to-string`
|
|
8
10
|
*
|
|
11
|
+
* Like React's `Children` utilities, this resolves lazy values (e.g. elements from server
|
|
12
|
+
* components), which suspends while one is still loading, so call it during render.
|
|
13
|
+
*
|
|
9
14
|
* @param reactNode - The React node to convert to string.
|
|
10
15
|
* @returns The string representation of the node, or empty string for null/undefined/boolean.
|
|
11
16
|
*/
|
|
12
17
|
export function reactNodeToString(reactNode: ReactNode): string {
|
|
18
|
+
if (isLazy(reactNode)) return reactNodeToString(resolveLazy(reactNode))
|
|
19
|
+
|
|
13
20
|
if (typeof reactNode === 'string') {
|
|
14
21
|
return reactNode
|
|
15
22
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Children, isValidElement, type ReactElement, type ReactNode } from 'react'
|
|
2
2
|
|
|
3
|
+
import { isLazy, resolveLazy } from './internal/index.ts'
|
|
4
|
+
|
|
3
5
|
type Side = 'start' | 'end' | 'both'
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -15,6 +17,9 @@ type Side = 'start' | 'end' | 'both'
|
|
|
15
17
|
* When recursing into a non-string edge child (e.g. a nested fragment), only the corresponding side
|
|
16
18
|
* is trimmed, so that whitespace which is internal to the outer node is preserved.
|
|
17
19
|
*
|
|
20
|
+
* Like React's `Children` utilities, this resolves lazy values (e.g. elements from server
|
|
21
|
+
* components), which suspends while one is still loading, so call it during render.
|
|
22
|
+
*
|
|
18
23
|
* @example
|
|
19
24
|
* ````tsx
|
|
20
25
|
* trimReactNode(' hello ') // 'hello'
|
|
@@ -30,6 +35,8 @@ type Side = 'start' | 'end' | 'both'
|
|
|
30
35
|
* @returns A new `ReactNode` with the requested edge whitespace removed.
|
|
31
36
|
*/
|
|
32
37
|
export function trimReactNode(reactNode: ReactNode, side: Side = 'both'): ReactNode {
|
|
38
|
+
if (isLazy(reactNode)) return trimReactNode(resolveLazy(reactNode), side)
|
|
39
|
+
|
|
33
40
|
// Handle primitives
|
|
34
41
|
if (typeof reactNode === 'string') {
|
|
35
42
|
switch (side) {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"styleScheduler.d.ts","sourceRoot":"","sources":["../../src/utils/styleScheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,cAAM,cAAc;IAClB,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,OAAO,CAAqB;IAEpC;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAiBzC;IAED;;;OAGG;IACH,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAM/D;CACF;AAGD,eAAO,MAAM,cAAc,gBAAuB,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"styleScheduler.js","sourceRoot":"","sources":["../../src/utils/styleScheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,cAAc;IACV,KAAK,GAAkB,IAAI,CAAA;IAC3B,OAAO,GAAmB,EAAE,CAAA;IAEpC;;;;OAIG;IACH,QAAQ,CAAC,QAAoB;QAC3B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAE3B,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YACxB,IAAI,CAAC,KAAK,GAAG,qBAAqB,CAAC,GAAG,EAAE;gBACtC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAA;gBAC9B,IAAI,CAAC,OAAO,GAAG,EAAE,CAAA;gBACjB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;gBAEjB,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;YACjC,CAAC,CAAC,CAAA;QACJ,CAAC;QAED,OAAO,GAAG,EAAE;YACV,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;YAC5C,IAAI,KAAK,KAAK,CAAC,CAAC;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;QACjD,CAAC,CAAA;IACH,CAAC;IAED;;;OAGG;IACH,gBAAgB,CAAC,OAAgB;QAC/B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;gBACjB,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAA;YAC3C,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;IACJ,CAAC;CACF;AAED,kDAAkD;AAClD,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,cAAc,EAAE,CAAA"}
|