@yak/react 0.0.0-stage → 10.0.0-rc.1

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 (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -2
  3. package/dist/context/baseContext.d.ts +5 -0
  4. package/dist/context/baseContext.js +8 -0
  5. package/dist/context/baseContext.js.map +1 -0
  6. package/dist/context/index.d.ts +20 -0
  7. package/dist/context/index.js +15 -0
  8. package/dist/context/index.js.map +1 -0
  9. package/dist/context/index.server.d.ts +9 -0
  10. package/dist/context/index.server.js +17 -0
  11. package/dist/context/index.server.js.map +1 -0
  12. package/dist/cross-file-resolver/index.d.ts +199 -0
  13. package/dist/cross-file-resolver/index.js +569 -0
  14. package/dist/cross-file-resolver/index.js.map +1 -0
  15. package/dist/index.d.ts +320 -0
  16. package/dist/index.js +2 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/internal.d.ts +452 -0
  19. package/dist/internal.js +363 -0
  20. package/dist/internal.js.map +1 -0
  21. package/dist/isolated-source-eval/index.d.ts +30 -0
  22. package/dist/isolated-source-eval/index.js +317 -0
  23. package/dist/isolated-source-eval/index.js.map +1 -0
  24. package/dist/isolated-source-eval/worker.js +59 -0
  25. package/dist/isolated-source-eval/worker.js.map +1 -0
  26. package/dist/jsx-dev-runtime.d.ts +7 -0
  27. package/dist/jsx-dev-runtime.js +2 -0
  28. package/dist/jsx-dev-runtime.js.map +1 -0
  29. package/dist/jsx-runtime.d.ts +38 -0
  30. package/dist/jsx-runtime.js +2 -0
  31. package/dist/jsx-runtime.js.map +1 -0
  32. package/dist/loaders/turbo-loader.d.ts +15 -0
  33. package/dist/loaders/turbo-loader.js +816 -0
  34. package/dist/loaders/turbo-loader.js.map +1 -0
  35. package/dist/loaders/vite-plugin.d.ts +103 -0
  36. package/dist/loaders/vite-plugin.js +885 -0
  37. package/dist/loaders/vite-plugin.js.map +1 -0
  38. package/dist/loaders/webpack-loader.d.ts +12 -0
  39. package/dist/loaders/webpack-loader.js +710 -0
  40. package/dist/loaders/webpack-loader.js.map +1 -0
  41. package/dist/rsbuild/index.d.ts +29 -0
  42. package/dist/rsbuild/index.js +51 -0
  43. package/dist/rsbuild/index.js.map +1 -0
  44. package/dist/static/chunk-DK3Fl9T5.js +1 -0
  45. package/dist/static/index.d.ts +28 -0
  46. package/dist/static/index.js +2 -0
  47. package/dist/static/index.js.map +1 -0
  48. package/dist/withYak/index.d.ts +112 -0
  49. package/dist/withYak/index.js +171 -0
  50. package/dist/withYak/index.js.map +1 -0
  51. package/loaders/lib/resolveCrossFileSelectors.ts +161 -0
  52. package/loaders/turbo-evaluator.ts +134 -0
  53. package/loaders/turbo-loader.ts +184 -0
  54. package/loaders/vite-plugin.ts +28 -0
  55. package/loaders/webpack-loader.ts +41 -0
  56. package/package.json +155 -3
  57. package/rsbuild/index.ts +91 -0
  58. package/runtime/__tests__/atoms.test.tsx +48 -0
  59. package/runtime/__tests__/attrs.test.tsx +490 -0
  60. package/runtime/__tests__/cssLiteral.test.tsx +503 -0
  61. package/runtime/__tests__/cssProp.test.tsx +392 -0
  62. package/runtime/__tests__/cssPropTest.tsx +227 -0
  63. package/runtime/__tests__/foldedJsxParity.test.tsx +117 -0
  64. package/runtime/__tests__/styled.test.tsx +653 -0
  65. package/runtime/__tests__/tsconfig.json +18 -0
  66. package/runtime/__tests__/typeTest.tsx +374 -0
  67. package/runtime/atoms.tsx +36 -0
  68. package/runtime/context/README.md +84 -0
  69. package/runtime/context/baseContext.tsx +5 -0
  70. package/runtime/context/index.server.tsx +22 -0
  71. package/runtime/context/index.tsx +36 -0
  72. package/runtime/cssLiteral.tsx +214 -0
  73. package/runtime/globalStyle.tsx +54 -0
  74. package/runtime/index.ts +37 -0
  75. package/runtime/internal.ts +42 -0
  76. package/runtime/internals/mergeClassNames.ts +23 -0
  77. package/runtime/internals/mergeCssProp.ts +69 -0
  78. package/runtime/internals/propMarkers.ts +20 -0
  79. package/runtime/internals/unitPostFix.ts +34 -0
  80. package/runtime/jsx-dev-runtime.ts +6 -0
  81. package/runtime/jsx-runtime.ts +48 -0
  82. package/runtime/keyframes.tsx +30 -0
  83. package/runtime/mocks/cssLiteral.ts +74 -0
  84. package/runtime/mocks/globalStyle.ts +14 -0
  85. package/runtime/mocks/keyframes.ts +29 -0
  86. package/runtime/mocks/styled.ts +8 -0
  87. package/runtime/publicStyledApi.ts +159 -0
  88. package/runtime/styled.tsx +372 -0
  89. package/runtime/styledDom.tsx +139 -0
  90. package/withYak/allow-global-css.ts +94 -0
  91. package/withYak/index.ts +268 -0
@@ -0,0 +1,29 @@
1
+ import type { keyframes as keyframesInternal } from "../keyframes.js";
2
+
3
+ /**
4
+ * Allows to use CSS keyframe animations in a styled or css block
5
+ *
6
+ * @usage
7
+ *
8
+ * ```tsx
9
+ * import { styled, keyframes } from "@yak/react";
10
+ *
11
+ * const rotate = keyframes`
12
+ * from {
13
+ * transform: rotate(0deg);
14
+ * }
15
+ * to {
16
+ * transform: rotate(360deg);
17
+ * }
18
+ * `;
19
+ *
20
+ * const Spinner = styled.div`
21
+ * animation: ${rotate} 1s linear infinite;
22
+ * `;
23
+ * ```
24
+ */
25
+ export const keyframes: typeof keyframesInternal = (_styles, ..._dynamic) => {
26
+ // the keyframes function is a no-op in the mock
27
+ // as it has no dynamic runtime behavior but only css
28
+ return "";
29
+ };
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ import { styled as StyledFactory } from "../styled.js";
3
+
4
+ export const styled = new Proxy(StyledFactory, {
5
+ get(target, TagName: keyof React.JSX.IntrinsicElements) {
6
+ return target(TagName);
7
+ },
8
+ }) as typeof StyledFactory;
@@ -0,0 +1,159 @@
1
+ // This is the public facing API for the styled object.
2
+ import React from "react";
3
+ import type { YakTheme } from "./context/index.js";
4
+ import { CSSInterpolation, yakComponentSymbol } from "./cssLiteral.js";
5
+
6
+ /**
7
+ * Main styled interface that combines HTML tag mappings with the styled function.
8
+ * This is the primary entry point for creating styled components.
9
+ */
10
+ export interface Styled extends MappedHtmlTags, StyledFn {}
11
+
12
+ /**
13
+ * Function interface for creating styled components from any component or HTML tag.
14
+ * Supports React components, HTML tags, and custom web components.
15
+ */
16
+ export interface StyledFn {
17
+ <TProps extends object = React.DOMAttributes<Element> & React.RefAttributes<Element>>(
18
+ Component: HtmlTags | React.FunctionComponent<TProps> | CustomWebComponentTag,
19
+ ): LiteralWithAttrs<TProps>;
20
+ }
21
+
22
+ /**
23
+ * A yak component with a special symbol that allows component targeting
24
+ * and proper attrs function handling.
25
+ * @example styled.svg`${Button}:hover & { fill: red; }` or styled(Button)`color: red;`
26
+ */
27
+ export interface YakComponent<T> extends React.FunctionComponent<T> {
28
+ // This is intentionally typed to hide the internal implementation details.
29
+ [yakComponentSymbol]: [unknown, unknown, unknown, unknown];
30
+ }
31
+
32
+ /**
33
+ * Styled component with attrs method for adding default props.
34
+ * Extends StyledLiteral with the ability to specify default attributes.
35
+ */
36
+ export interface LiteralWithAttrs<T extends object> extends StyledLiteral<T> {
37
+ attrs: <
38
+ TAttrsIn extends object = {},
39
+ TAttrsOut extends AttrsMerged<T, TAttrsIn> = AttrsMerged<T, TAttrsIn>,
40
+ >(
41
+ attrs: Attrs<T, TAttrsIn, TAttrsOut>,
42
+ ) => StyledLiteral<Substitute<T, TAttrsIn>>;
43
+ }
44
+
45
+ /**
46
+ * Template literal function for defining CSS styles with interpolation support.
47
+ * Accepts CSS template strings and interpolated values with proper typing.
48
+ */
49
+ export interface StyledLiteral<T> {
50
+ <TCSSProps>(
51
+ styles: TemplateStringsArray,
52
+ ...values: Array<
53
+ CSSInterpolation<
54
+ T &
55
+ // Prevent TypeScript from inferring types from template literal usage
56
+ // This ensures proper typing and enables destructuring hints
57
+ NoInfer<TCSSProps> & { theme: YakTheme }
58
+ >
59
+ >
60
+ ): YakComponent<TCSSProps & T>;
61
+ }
62
+
63
+ /**
64
+ * Function variant of attrs that receives current props and returns additional props.
65
+ * Allows for dynamic prop generation based on component state.
66
+ */
67
+ export interface AttrsFunction<
68
+ TBaseProps,
69
+ TIn extends object = {},
70
+ TOut extends AttrsMerged<TBaseProps, TIn> = AttrsMerged<TBaseProps, TIn>,
71
+ > {
72
+ (p: Substitute<TBaseProps & { theme: YakTheme }, TIn>): Partial<TOut>;
73
+ }
74
+
75
+ /**
76
+ * Merges provided props with initial props, making specified props optional.
77
+ * Includes theme support for styled components.
78
+ */
79
+ export type AttrsMerged<TBaseProps, TIn extends object = {}> = Substitute<
80
+ TBaseProps & { theme?: YakTheme },
81
+ TIn
82
+ >;
83
+
84
+ /**
85
+ * Maps all HTML tag names to their corresponding styled component types with attributes support.
86
+ * Provides typed access to all standard HTML elements through the styled interface.
87
+ */
88
+ export type MappedHtmlTags = {
89
+ [Tag in HtmlTags]: LiteralWithAttrs<React.JSX.IntrinsicElements[Tag]>;
90
+ };
91
+
92
+ /**
93
+ * The attrs function allows adding additional props to a styled component.
94
+ * Props can be specified as an object or as a function that receives current props.
95
+ */
96
+ export type Attrs<
97
+ TBaseProps,
98
+ TIn extends object = {},
99
+ TOut extends AttrsMerged<TBaseProps, TIn> = AttrsMerged<TBaseProps, TIn>,
100
+ > = Partial<TOut> | AttrsFunction<TBaseProps, TIn, TOut>;
101
+
102
+ /**
103
+ * Utility type to merge two object types, with properties from B taking precedence.
104
+ * If a property exists in both A and B, the property from B is used.
105
+ */
106
+ export type Substitute<A extends object, B extends object> = FastOmit<A, keyof B> & B;
107
+
108
+ /**
109
+ * Union type of all valid HTML element tag names.
110
+ * Derived from React's JSX intrinsic elements.
111
+ */
112
+ export type HtmlTags = keyof React.JSX.IntrinsicElements;
113
+
114
+ /**
115
+ * Custom web component tag pattern that must contain at least one hyphen.
116
+ * Follows the web component naming convention.
117
+ */
118
+ export type CustomWebComponentTag = `${string}-${string}`;
119
+
120
+ /**
121
+ * Utility type to efficiently remove properties from an object type.
122
+ * More performant than the built-in Omit type for large object types.
123
+ */
124
+ export type FastOmit<T extends object, U extends string | number | symbol> = {
125
+ [K in keyof T as K extends U ? never : K]: T[K];
126
+ };
127
+
128
+ /**
129
+ * Set-like collector for class names
130
+ *
131
+ * The runtime implements this as a string builder, a real `Set<string>`
132
+ * satisfies it as well
133
+ */
134
+ export type ClassNameCollector = {
135
+ add(className: string): void;
136
+ has(className: string): boolean;
137
+ delete(className: string): void;
138
+ };
139
+
140
+ /**
141
+ * Type of all functions that can be passed to manipulate styles
142
+ *
143
+ * `$dynamic` marks processors that execute user functions or write style
144
+ * values at render time; purely static processors (class names only) can
145
+ * skip theme lookup and style-object allocation.
146
+ */
147
+ export type RuntimeStyleProcessor<T> = ((
148
+ props: T,
149
+ classNames: ClassNameCollector,
150
+ style: React.CSSProperties,
151
+ ) => void) & { $dynamic?: boolean };
152
+
153
+ /**
154
+ * Utility type to keep the generic API of a component while still being able to use it in a selector
155
+ */
156
+ export type GenericYakComponentOf<T, P = {}> = T &
157
+ YakComponent<P> & {
158
+ <G = {}>(props: P & G): React.ReactElement | null;
159
+ };
@@ -0,0 +1,372 @@
1
+ import { css, CSSInterpolation, ClassNames, yakComponentSymbol } from "./cssLiteral.js";
2
+ import * as INTERNAL from "./internals/propMarkers.js";
3
+ import React from "react";
4
+ import type {
5
+ Attrs,
6
+ AttrsMerged,
7
+ Styled,
8
+ YakComponent,
9
+ AttrsFunction,
10
+ StyledFn,
11
+ HtmlTags,
12
+ Substitute,
13
+ StyledLiteral,
14
+ RuntimeStyleProcessor,
15
+ } from "./publicStyledApi.js";
16
+
17
+ // the following export is not relative as "@yak/react/context"
18
+ // links to one file for react server components and
19
+ // to another file for classic react components
20
+ import { useTheme } from "@yak/react/context";
21
+ import type { YakTheme } from "./context/index.tsx";
22
+ import { mergeClassNames } from "./internals/mergeClassNames.js";
23
+
24
+ //
25
+ // The `styled()` API without `styled.` syntax
26
+ //
27
+ // The API design is inspired by styled-components:
28
+ // https://github.com/styled-components/styled-components/blob/main/packages/styled-components/src/constructors/styled.tsx
29
+ // https://github.com/styled-components/styled-components/blob/main/packages/styled-components/src/models/StyledComponent.ts
30
+ //
31
+ const styledFactory: StyledFn = (Component) =>
32
+ Object.assign(yakStyled(Component), {
33
+ attrs: (attrs: Attrs<any>) => yakStyled(Component, attrs),
34
+ });
35
+
36
+ /**
37
+ * The `styled` method works perfectly on all of your own or any third-party component,
38
+ * as long as they attach the passed className prop to a DOM element.
39
+ *
40
+ * @usage
41
+ *
42
+ * ```tsx
43
+ * const StyledLink = styled(Link)`
44
+ * color: #BF4F74;
45
+ * font-weight: bold;
46
+ * `;
47
+ * ```
48
+ */
49
+ export const styled = styledFactory as Styled;
50
+
51
+ /**
52
+ * Real shape of the yakComponentSymbol tuple, which the public `YakComponent`
53
+ * keeps opaque as `[unknown, ...]`
54
+ */
55
+ type YakComponentInternals = [
56
+ self: React.FunctionComponent,
57
+ attrsFn: AttrsFunction<any, any, any> | undefined,
58
+ styleProcessor: RuntimeStyleProcessor<unknown>,
59
+ target: React.FunctionComponent | string,
60
+ ];
61
+
62
+ const yakStyled: StyledInternal = (Component, attrs) => {
63
+ const isYakComponent = typeof Component !== "string" && yakComponentSymbol in Component;
64
+
65
+ // if the component that is wrapped is a yak component, we can extract it to render the underlying component directly
66
+ // and we can also extract the attrs function and the dynamic style function to merge it with the current attrs function (or dynamic style function)
67
+ // so that the sequence of the attrs functions is preserved
68
+ const [, parentAttrsFn, parentRuntimeStylesFn, parentTarget] = isYakComponent
69
+ ? (Component[yakComponentSymbol] as [
70
+ YakComponent<unknown>,
71
+ ExtractAttrsFunction<typeof attrs>,
72
+ RuntimeStyleProcessor<unknown>,
73
+ React.FunctionComponent | string,
74
+ ])
75
+ : [];
76
+
77
+ // the ultimate render target of the whole styled(styled(...)) chain:
78
+ // attrs and style processors are already merged at construction time, so
79
+ // a chain of N levels renders the target directly in ONE wrapper instead
80
+ // of re-entering every parent wrapper per element per render
81
+ const targetComponent = (isYakComponent ? parentTarget : Component) as
82
+ | React.FunctionComponent
83
+ | string;
84
+
85
+ const mergedAttrsFn = buildRuntimeAttrsProcessor(attrs, parentAttrsFn);
86
+ const staticAttrs = (mergedAttrsFn as StaticAttrsCarrier | undefined)?.[INTERNAL.STATIC_ATTRS];
87
+
88
+ return (styles, ...values) => {
89
+ // combine all interpolated logic into a single function
90
+ // e.g. styled.button`color: ${props => props.color}; margin: ${props => props.margin};`
91
+ const runtimeStylesFn = css(
92
+ styles,
93
+ ...(values as CSSInterpolation<unknown>[]),
94
+ ) as RuntimeStyleProcessor<unknown>;
95
+ const runtimeStyleProcessor = buildRuntimeStylesProcessor(
96
+ runtimeStylesFn,
97
+ parentRuntimeStylesFn,
98
+ );
99
+ const Yak: React.FunctionComponent = (props) => {
100
+ // fast path for components that contribute the same thing on every
101
+ // render — no attrs at all, or a constant `.attrs({...})` which cannot
102
+ // read the theme — and no dynamic styles. Contributes the chain's class
103
+ // names and strips $-props; skips theme lookup, prop spreading and style
104
+ // cloning entirely (this is NOT against the rule of hooks — the condition
105
+ // is constant for the lifetime of the component)
106
+ if ((!mergedAttrsFn || staticAttrs) && !runtimeStyleProcessor.$dynamic) {
107
+ // props that already went through a yak wrapper keep their processed
108
+ // className, and `source` then aliases `props` — so the class names are
109
+ // written to the fresh, filtered object rather than back into props
110
+ const source = (
111
+ staticAttrs && !(INTERNAL.ATTRS_MERGED in props)
112
+ ? combineProps(
113
+ {
114
+ ...(props as { className?: string; style?: React.CSSProperties }),
115
+ // mark the props as processed
116
+ [INTERNAL.ATTRS_MERGED]: true,
117
+ },
118
+ staticAttrs,
119
+ )
120
+ : props
121
+ ) as { className?: string; style?: React.CSSProperties };
122
+ const filteredProps = removeNonDomProperties(source) as {
123
+ className?: string;
124
+ };
125
+ if (!(INTERNAL.RUNTIME_STYLES_DONE in source)) {
126
+ const classNames = new ClassNames(source.className);
127
+ runtimeStyleProcessor(source, classNames, undefined as unknown as React.CSSProperties);
128
+ filteredProps.className = classNames.value || undefined;
129
+ }
130
+ const Target = targetComponent as React.ElementType;
131
+ return <Target {...(filteredProps as React.ComponentProps<typeof Target>)} />;
132
+ }
133
+
134
+ // attrs functions and dynamic style functions receive the theme —
135
+ // fully static components take the fast path above and never read the
136
+ // theme context
137
+ const theme = useTheme();
138
+
139
+ // The first components which is not wrapped in a yak component will execute all attrs functions
140
+ // starting from the innermost yak component to the outermost yak component (itself)
141
+ const combinedProps =
142
+ INTERNAL.ATTRS_MERGED in props
143
+ ? ({
144
+ theme,
145
+ ...props,
146
+ } as {
147
+ theme: YakTheme;
148
+ className?: string;
149
+ style?: React.CSSProperties;
150
+ })
151
+ : // overwrite and merge the current props with the processed attrs
152
+ combineProps(
153
+ {
154
+ theme,
155
+ ...(props as {
156
+ className?: string;
157
+ style?: React.CSSProperties;
158
+ }),
159
+ // mark the props as processed
160
+ [INTERNAL.ATTRS_MERGED]: true,
161
+ },
162
+ mergedAttrsFn?.({ theme, ...(props as any) }),
163
+ );
164
+
165
+ // execute all functions inside the style literal if not already executed
166
+ // e.g. styled.button`color: ${props => props.color};`
167
+ //
168
+ // inner levels of a styled(Component) chain receive already-processed
169
+ // props and skip this entirely — no collector, no style clone
170
+ if (!(INTERNAL.RUNTIME_STYLES_DONE in combinedProps)) {
171
+ const classNames = new ClassNames(combinedProps.className);
172
+ // static processors never write style values, so the incoming style
173
+ // object can be passed through without a defensive copy
174
+ const styles = runtimeStyleProcessor.$dynamic
175
+ ? { ...combinedProps.style }
176
+ : combinedProps.style;
177
+ runtimeStyleProcessor(combinedProps, classNames, styles as React.CSSProperties);
178
+ // @ts-expect-error this is not typed correctly
179
+ combinedProps[INTERNAL.RUNTIME_STYLES_DONE] = true;
180
+
181
+ combinedProps.className = classNames.value || undefined;
182
+ if (styles !== combinedProps.style) {
183
+ combinedProps.style = styles;
184
+ }
185
+ }
186
+
187
+ // delete the yak theme from the props
188
+ // this must happen after the runtimeStyles are calculated
189
+ // prevents passing the theme prop to the DOM element of a styled component
190
+ const { theme: themeAfterAttr, ...combinedPropsWithoutTheme } = combinedProps;
191
+ const propsBeforeFiltering =
192
+ themeAfterAttr === theme ? combinedPropsWithoutTheme : combinedProps;
193
+
194
+ // remove all props that start with a $ sign so they reach neither DOM
195
+ // elements nor custom components — this also strips the internal
196
+ // INTERNAL.ATTRS_MERGED/INTERNAL.RUNTIME_STYLES_DONE markers, which must not cross a
197
+ // custom component boundary (a custom component may render another yak
198
+ // component that has to process its own attrs/styles)
199
+ const filteredProps = removeNonDomProperties(propsBeforeFiltering);
200
+
201
+ // render the chain's target directly — parent wrappers contribute only
202
+ // their (already merged) attrs and style processors
203
+ const Target = targetComponent as React.ElementType;
204
+ return <Target {...(filteredProps as React.ComponentProps<typeof Target>)} />;
205
+ };
206
+
207
+ // Direct write instead of Object.assign (faster & smaller)
208
+ const taggedYak = Yak as React.FunctionComponent & {
209
+ [yakComponentSymbol]: YakComponentInternals;
210
+ };
211
+ taggedYak[yakComponentSymbol] = [Yak, mergedAttrsFn, runtimeStyleProcessor, targetComponent];
212
+ return taggedYak;
213
+ };
214
+ };
215
+
216
+ /**
217
+ * Remove all entries that start with a $ sign
218
+ *
219
+ * This allows to have props that are used for internal styling purposes
220
+ * but are not be passed to the DOM element
221
+ */
222
+ const removeNonDomProperties = <T extends Record<string, unknown>>(obj: T): T => {
223
+ const result = {} as T;
224
+ for (const key in obj) {
225
+ if (!key.startsWith("$") && obj[key] !== undefined) {
226
+ result[key] = obj[key];
227
+ }
228
+ }
229
+ return result;
230
+ };
231
+
232
+ /**
233
+ * merge props and processed props (including class names and styles)
234
+ * e.g.:\
235
+ * `{ className: "a", foo: 1 }` and `{ className: "b", bar: 2 }` \
236
+ * => `{ className: "a b", foo: 1, bar: 2 }`
237
+ */
238
+ const combineProps = <
239
+ T extends {
240
+ className?: string;
241
+ style?: React.CSSProperties;
242
+ },
243
+ TOther extends
244
+ | {
245
+ className?: string;
246
+ style?: React.CSSProperties;
247
+ }
248
+ | null
249
+ | undefined,
250
+ >(
251
+ props: T,
252
+ newProps: TOther,
253
+ ) =>
254
+ newProps
255
+ ? (props.className === newProps.className || !newProps.className) &&
256
+ (props.style === newProps.style || !newProps.style)
257
+ ? // shortcut if no style and class merging is necessary
258
+ {
259
+ ...props,
260
+ ...newProps,
261
+ }
262
+ : // merge class names and styles
263
+ {
264
+ ...props,
265
+ ...newProps,
266
+ className: mergeClassNames(props.className, newProps.className),
267
+ style: { ...props.style, ...newProps.style },
268
+ }
269
+ : // if no new props are provided, no merging is necessary
270
+ props;
271
+
272
+ /**
273
+ * Merges the attrs function of the current component with the attrs function of the parent component
274
+ * in order to preserve the sequence of the attrs functions.
275
+ * Note: In theory, the parentAttrsFn can have different types for TAttrsIn and TAttrsOut
276
+ * but as this is only used internally, we can ignore and simplify this case
277
+ * @param attrs The attrs object or function of the current component (if any)
278
+ * @param parentAttrsFn The attrs function of the parent/wrapped component (if any)
279
+ * @returns A function that receives the props and returns the transformed props
280
+ */
281
+ const buildRuntimeAttrsProcessor = <
282
+ T,
283
+ TAttrsIn extends object,
284
+ TAttrsOut extends AttrsMerged<T, TAttrsIn>,
285
+ >(
286
+ attrs?: Attrs<T, TAttrsIn, TAttrsOut>,
287
+ parentAttrsFn?: AttrsFunction<T, TAttrsIn, TAttrsOut>,
288
+ ): AttrsFunction<T, TAttrsIn, TAttrsOut> | undefined => {
289
+ const ownAttrsFn = attrs && (typeof attrs === "function" ? attrs : () => attrs);
290
+
291
+ if (ownAttrsFn && parentAttrsFn) {
292
+ return (props) => {
293
+ const parentProps = parentAttrsFn(props);
294
+
295
+ // overwrite and merge the parent props with the props received from the attrs function
296
+ // after they went through the parent attrs function.
297
+ //
298
+ // This makes sure the linearity of the attrs functions is preserved and all attrs function receive
299
+ // the whole props object calculated from the previous attrs functions
300
+ return combineProps(parentProps, ownAttrsFn(combineProps(props, parentProps)));
301
+ };
302
+ }
303
+
304
+ // A constant `.attrs({...})` is wrapped into `() => attrs`, which makes it
305
+ // indistinguishable from `.attrs(props => ...)` at render time — so record the
306
+ // object it will always return. Only the wrapper closure is tagged, never a
307
+ // user-supplied attrs function.
308
+ //
309
+ // A `styled(StyledWithAttrs).attrs({...})` chain merges its levels per render
310
+ // and is not tagged, which keeps a mutation of an attrs object observable
311
+ if (ownAttrsFn && typeof attrs !== "function") {
312
+ return Object.assign(ownAttrsFn, {
313
+ [INTERNAL.STATIC_ATTRS]: attrs,
314
+ } as StaticAttrsCarrier);
315
+ }
316
+
317
+ return ownAttrsFn || parentAttrsFn;
318
+ };
319
+
320
+ /**
321
+ * The constant object a `.attrs({...})` processor always returns
322
+ *
323
+ * Kept local to this module — like the `yakComponentSymbol` tuple, it is an
324
+ * implementation detail and must not reach the public `AttrsFunction` type
325
+ */
326
+ type StaticAttrsCarrier = { [K in typeof INTERNAL.STATIC_ATTRS]?: object };
327
+
328
+ /**
329
+ * Merges the runtime style function of the current component with the runtime style function of the parent component
330
+ * in order to preserve the sequence of the attrs functions.
331
+ * @param runtimeStylesFn The current runtime styles function
332
+ * @param parentRuntimeStylesFn The parent runtime styles function
333
+ * @returns The merged runtime styles function
334
+ */
335
+ const buildRuntimeStylesProcessor = <T,>(
336
+ runtimeStylesFn: RuntimeStyleProcessor<T>,
337
+ parentRuntimeStylesFn?: RuntimeStyleProcessor<T>,
338
+ ) => {
339
+ if (runtimeStylesFn && parentRuntimeStylesFn) {
340
+ const combined: RuntimeStyleProcessor<T> = Object.assign(
341
+ (
342
+ props: T,
343
+ classNames: Parameters<RuntimeStyleProcessor<T>>[1],
344
+ style: React.CSSProperties,
345
+ ) => {
346
+ parentRuntimeStylesFn(props, classNames, style);
347
+ runtimeStylesFn(props, classNames, style);
348
+ },
349
+ // the chain is dynamic if any level is dynamic
350
+ { $dynamic: runtimeStylesFn.$dynamic || parentRuntimeStylesFn.$dynamic },
351
+ );
352
+ return combined;
353
+ }
354
+ return runtimeStylesFn || parentRuntimeStylesFn;
355
+ };
356
+
357
+ /**
358
+ * Internal function where attrs are passed to be processed
359
+ */
360
+ export type StyledInternal = <
361
+ T extends object,
362
+ TAttrsIn extends object = {},
363
+ TAttrsOut extends AttrsMerged<T, TAttrsIn> = AttrsMerged<T, TAttrsIn>,
364
+ >(
365
+ Component: React.FunctionComponent<T> | YakComponent<T> | HtmlTags | string,
366
+ attrs?: Attrs<T, TAttrsIn, TAttrsOut>,
367
+ ) => StyledLiteral<Substitute<T, TAttrsIn>>;
368
+
369
+ /**
370
+ * Utility type to extract the AttrsFunction from the Attrs type
371
+ */
372
+ export type ExtractAttrsFunction<T> = T extends (p: any) => any ? T : never;