@pyreon/unistyle 0.24.5 → 0.24.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 (50) hide show
  1. package/package.json +7 -9
  2. package/src/__tests__/alignContent.test.ts +0 -121
  3. package/src/__tests__/borderRadius.test.ts +0 -125
  4. package/src/__tests__/camelToKebab.test.ts +0 -44
  5. package/src/__tests__/context.test.ts +0 -147
  6. package/src/__tests__/createMediaQueries.test.ts +0 -98
  7. package/src/__tests__/edge.test.ts +0 -164
  8. package/src/__tests__/enrichTheme.test.ts +0 -56
  9. package/src/__tests__/extendCss.test.ts +0 -45
  10. package/src/__tests__/index.test.ts +0 -79
  11. package/src/__tests__/makeItResponsive.test.ts +0 -431
  12. package/src/__tests__/manifest-snapshot.test.ts +0 -34
  13. package/src/__tests__/native-marker.test.ts +0 -9
  14. package/src/__tests__/optimizeBreakpointDeltas.test.ts +0 -124
  15. package/src/__tests__/processDescriptor.test.ts +0 -322
  16. package/src/__tests__/responsive.test.ts +0 -221
  17. package/src/__tests__/special-keys.test.ts +0 -120
  18. package/src/__tests__/styles.test.ts +0 -273
  19. package/src/__tests__/unistyle.browser.test.tsx +0 -169
  20. package/src/__tests__/units.test.ts +0 -134
  21. package/src/context.tsx +0 -44
  22. package/src/enrichTheme.ts +0 -42
  23. package/src/env.d.ts +0 -6
  24. package/src/index.ts +0 -91
  25. package/src/manifest.ts +0 -197
  26. package/src/responsive/breakpoints.ts +0 -15
  27. package/src/responsive/createMediaQueries.ts +0 -43
  28. package/src/responsive/index.ts +0 -15
  29. package/src/responsive/makeItResponsive.ts +0 -223
  30. package/src/responsive/normalizeTheme.ts +0 -79
  31. package/src/responsive/optimizeBreakpointDeltas.ts +0 -190
  32. package/src/responsive/optimizeTheme.ts +0 -60
  33. package/src/responsive/sortBreakpoints.ts +0 -10
  34. package/src/responsive/transformTheme.ts +0 -54
  35. package/src/styles/alignContent.ts +0 -62
  36. package/src/styles/extendCss.ts +0 -26
  37. package/src/styles/index.ts +0 -16
  38. package/src/styles/shorthands/borderRadius.ts +0 -89
  39. package/src/styles/shorthands/edge.ts +0 -108
  40. package/src/styles/shorthands/index.ts +0 -4
  41. package/src/styles/styles/camelToKebab.ts +0 -3
  42. package/src/styles/styles/index.ts +0 -132
  43. package/src/styles/styles/processDescriptor.ts +0 -136
  44. package/src/styles/styles/propertyMap.ts +0 -438
  45. package/src/styles/styles/types.ts +0 -368
  46. package/src/types.ts +0 -175
  47. package/src/units/index.ts +0 -6
  48. package/src/units/stripUnit.ts +0 -25
  49. package/src/units/value.ts +0 -47
  50. package/src/units/values.ts +0 -40
package/src/index.ts DELETED
@@ -1,91 +0,0 @@
1
- import type { TProvider } from './context'
2
- import Provider, { context } from './context'
3
- import type { PyreonTheme } from './enrichTheme'
4
- import { enrichTheme } from './enrichTheme'
5
- import type {
6
- Breakpoints,
7
- CreateMediaQueries,
8
- MakeItResponsive,
9
- MakeItResponsiveStyles,
10
- NormalizeTheme,
11
- SortBreakpoints,
12
- TransformTheme,
13
- } from './responsive'
14
- import {
15
- breakpoints,
16
- createMediaQueries,
17
- makeItResponsive,
18
- normalizeTheme,
19
- sortBreakpoints,
20
- transformTheme,
21
- } from './responsive'
22
- import type {
23
- AlignContent,
24
- AlignContentAlignXKeys,
25
- AlignContentAlignYKeys,
26
- AlignContentDirectionKeys,
27
- ExtendCss,
28
- ITheme,
29
- Styles,
30
- StylesTheme,
31
- } from './styles'
32
- import {
33
- ALIGN_CONTENT_DIRECTION,
34
- ALIGN_CONTENT_MAP_X,
35
- ALIGN_CONTENT_MAP_Y,
36
- alignContent,
37
- extendCss,
38
- styles,
39
- } from './styles'
40
- import type { BrowserColors, Color, Defaults, PropertyValue, UnitValue } from './types'
41
- import type { StripUnit, Value, Values } from './units'
42
- import { stripUnit, value, values } from './units'
43
-
44
- export type {
45
- AlignContent,
46
- AlignContentAlignXKeys,
47
- AlignContentAlignYKeys,
48
- AlignContentDirectionKeys,
49
- Breakpoints,
50
- BrowserColors,
51
- Color,
52
- CreateMediaQueries,
53
- Defaults,
54
- ExtendCss,
55
- ITheme,
56
- MakeItResponsive,
57
- MakeItResponsiveStyles,
58
- NormalizeTheme,
59
- PropertyValue,
60
- PyreonTheme,
61
- SortBreakpoints,
62
- StripUnit,
63
- Styles,
64
- StylesTheme,
65
- TProvider,
66
- TransformTheme,
67
- UnitValue,
68
- Value,
69
- Values,
70
- }
71
-
72
- export {
73
- ALIGN_CONTENT_DIRECTION,
74
- ALIGN_CONTENT_MAP_X,
75
- ALIGN_CONTENT_MAP_Y,
76
- alignContent,
77
- breakpoints,
78
- context,
79
- createMediaQueries,
80
- enrichTheme,
81
- extendCss,
82
- makeItResponsive,
83
- normalizeTheme,
84
- Provider,
85
- sortBreakpoints,
86
- stripUnit,
87
- styles,
88
- transformTheme,
89
- value,
90
- values,
91
- }
package/src/manifest.ts DELETED
@@ -1,197 +0,0 @@
1
- import { defineManifest } from '@pyreon/manifest'
2
-
3
- export default defineManifest({
4
- name: '@pyreon/unistyle',
5
- title: 'Responsive CSS Utilities',
6
- tagline:
7
- 'Responsive breakpoints, CSS property mappings, unit utilities, theme enrichment',
8
- description:
9
- 'Foundational responsive-style layer that powers every visual package above it (`elements`, `rocketstyle`, `coolgrid`, `kinetic`). `enrichTheme()` merges a partial user theme with the default breakpoints / spacing / unit utilities so the rest of the system has a complete theme to read. `makeItResponsive()` turns a value or per-breakpoint map into the right CSS for the current screen. `createMediaQueries()` builds breakpoint-keyed media queries; `styles()` generates CSS from a theme; `alignContent()` resolves alignment shorthand to flex / grid CSS. The package is the single source of truth for responsive prop semantics across the UI system.',
10
- category: 'browser',
11
- features: [
12
- 'enrichTheme(theme) — merge a partial theme with default breakpoints / spacing / units',
13
- 'breakpoints() — default responsive breakpoint set',
14
- 'createMediaQueries(breakpoints) — build breakpoint-keyed media query strings',
15
- 'makeItResponsive() — resolve a value / array / breakpoint object to CSS for the current screen',
16
- 'styles(theme) — generate CSS from a theme',
17
- 'alignContent() — resolve alignX / alignY / direction to flex CSS',
18
- 'extendCss() — extend a CSS definition with overrides',
19
- 'stripUnit / value / values — unit-utility helpers',
20
- 'Provider / context — React-style provider for the theme (used internally by PyreonUI)',
21
- ],
22
- longExample: `import { enrichTheme, makeItResponsive, createMediaQueries, alignContent } from '@pyreon/unistyle'
23
-
24
- // 1. Enrich a partial user theme with defaults — required before passing to PyreonUI
25
- const theme = enrichTheme({
26
- colors: { primary: '#3b82f6', secondary: '#6366f1' },
27
- fonts: { body: 'Inter, sans-serif' },
28
- })
29
-
30
- // 2. Build media queries keyed by breakpoint name
31
- const queries = createMediaQueries(theme.breakpoints)
32
- // → { xs: '@media (min-width: 0)', sm: '@media (min-width: 640px)', md: '...', ... }
33
-
34
- // 3. Responsive props — single value, mobile-first array, or breakpoint object
35
- const padding = makeItResponsive({ value: [8, 12, 16], property: 'padding', theme })
36
- // → 'padding: 8px; @media (...) { padding: 12px } @media (...) { padding: 16px }'
37
-
38
- const padding2 = makeItResponsive({
39
- value: { xs: 8, md: 16, xl: 24 },
40
- property: 'padding',
41
- theme,
42
- })
43
-
44
- // 4. alignContent maps shorthand to flex CSS
45
- const flexCss = alignContent({ alignX: 'center', alignY: 'start', direction: 'row' })
46
- // → 'justify-content: center; align-items: flex-start;'`,
47
- api: [
48
- {
49
- name: 'enrichTheme',
50
- kind: 'function',
51
- signature: 'enrichTheme(theme: PartialTheme): Theme',
52
- summary:
53
- 'Merge a partial theme with the full default theme (breakpoints, spacing, unit utilities, fallback colors). Always call this before passing a user theme to `PyreonUI` — raw theme objects miss the default breakpoints and spacing scale that the rest of the UI system reads from. Idempotent: enriching an already-enriched theme is a no-op.',
54
- example: `import { enrichTheme } from "@pyreon/unistyle"
55
-
56
- const theme = enrichTheme({
57
- colors: { primary: "#3b82f6", secondary: "#6366f1" },
58
- fonts: { body: "Inter, sans-serif" },
59
- })
60
-
61
- // Merges user overrides with default breakpoints, spacing, and units`,
62
- mistakes: [
63
- 'Passing the raw partial theme to `<PyreonUI theme={...}>` without enriching — `theme.breakpoints` is undefined and every responsive prop falls back to the desktop value',
64
- 'Mutating the theme after passing it to `PyreonUI` — the styler resolver caches off the theme identity; clone + re-enrich for whole-theme swaps',
65
- ],
66
- seeAlso: ['breakpoints', 'createMediaQueries'],
67
- },
68
- {
69
- name: 'breakpoints',
70
- kind: 'function',
71
- signature: 'breakpoints(): Breakpoints',
72
- summary:
73
- 'Return the default breakpoint set keyed by name (`xs`, `sm`, `md`, `lg`, `xl`, `xxl`) with min-width values in pixels. The same map is folded into `enrichTheme()` output, so most consumers read `theme.breakpoints` rather than calling this directly. Use it when you need the defaults outside a theme context (e.g. building a custom theme programmatically).',
74
- example: `import { breakpoints } from '@pyreon/unistyle'
75
-
76
- const bp = breakpoints()
77
- // { xs: 0, sm: 640, md: 768, lg: 1024, xl: 1280, xxl: 1536 }`,
78
- seeAlso: ['enrichTheme', 'createMediaQueries'],
79
- },
80
- {
81
- name: 'createMediaQueries',
82
- kind: 'function',
83
- signature: 'createMediaQueries(breakpoints: Breakpoints): Record<string, string>',
84
- summary:
85
- 'Build a record of media-query strings keyed by breakpoint name. Each value is a `min-width` query — `xs` is `(min-width: 0)`, `sm` becomes `(min-width: 640px)`, and so on. Used internally by `makeItResponsive()`; expose to consumers when they need to compose custom CSS-in-JS rules outside the responsive-prop pipeline.',
86
- example: `import { createMediaQueries, breakpoints } from '@pyreon/unistyle'
87
-
88
- const queries = createMediaQueries(breakpoints())
89
- // { xs: '@media (min-width: 0)', sm: '@media (min-width: 640px)', md: '@media (min-width: 768px)', ... }`,
90
- seeAlso: ['breakpoints', 'makeItResponsive'],
91
- },
92
- {
93
- name: 'makeItResponsive',
94
- kind: 'function',
95
- signature:
96
- 'makeItResponsive<T>(options: { value: T | T[] | Record<string, T>; property: string; theme: Theme }): string',
97
- summary:
98
- 'Resolve a responsive prop value to CSS for the current screen. Accepts three input shapes: single value (applies at all breakpoints), mobile-first array `[xs, sm, md, lg]` (each entry maps to the next breakpoint), or breakpoint object `{ xs: ..., md: ..., xl: ... }` (named keys map directly). The output is a CSS string with media queries already embedded; insert into a styled component template literal.',
99
- example: `import { makeItResponsive } from '@pyreon/unistyle'
100
-
101
- makeItResponsive({ value: 16, property: 'padding', theme })
102
- // → 'padding: 16px;'
103
-
104
- makeItResponsive({ value: [8, 12, 16], property: 'padding', theme })
105
- // → 'padding: 8px; @media (min-width: 640px) { padding: 12px } @media (min-width: 768px) { padding: 16px }'
106
-
107
- makeItResponsive({ value: { xs: 8, md: 16, xl: 24 }, property: 'padding', theme })
108
- // → '@media (min-width: 0) { padding: 8px } @media (min-width: 768px) { padding: 16px } @media (min-width: 1280px) { padding: 24px }'`,
109
- mistakes: [
110
- 'Passing CSS-spec property names (`borderTopWidth`) — unistyle uses property-first naming (`borderWidthTop`); the responsive transformer expects the unistyle convention',
111
- 'Forgetting to pass an enriched theme — without `theme.breakpoints`, the array form falls back to the first value at every breakpoint',
112
- ],
113
- seeAlso: ['createMediaQueries', 'styles'],
114
- },
115
- {
116
- name: 'styles',
117
- kind: 'function',
118
- signature: 'styles(theme: Theme): string',
119
- summary:
120
- 'Generate the CSS string for a complete theme — colors, spacing, fonts, breakpoints, the works. Used to produce the cascade of CSS variables / global declarations that backs every styled component. Most consumers don\\\'t call this directly; the `PyreonUI` provider invokes it internally on theme mount.',
121
- example: `import { styles, enrichTheme } from '@pyreon/unistyle'
122
-
123
- const theme = enrichTheme({ colors: { primary: '#3b82f6' } })
124
- const css = styles(theme)
125
- // → ':root { --color-primary: #3b82f6; --spacing-xs: 4px; ... }'`,
126
- seeAlso: ['enrichTheme', 'extendCss'],
127
- },
128
- {
129
- name: 'alignContent',
130
- kind: 'function',
131
- signature:
132
- "alignContent(options: { alignX?: AlignXKey; alignY?: AlignYKey; direction?: 'row' | 'column' | 'inline' | 'rows' }): string",
133
- summary:
134
- 'Resolve `alignX` / `alignY` / `direction` shorthand to the matching flex / grid CSS (`justify-content`, `align-items`). The Element / Row / Column primitives use this internally — it\\\'s exposed for custom layout components that want the same alignment semantics. `direction: "inline"` maps to `row`; `direction: "rows"` maps to `column`.',
135
- example: `import { alignContent } from '@pyreon/unistyle'
136
-
137
- alignContent({ alignX: 'center', alignY: 'start', direction: 'row' })
138
- // → 'justify-content: center; align-items: flex-start;'
139
-
140
- alignContent({ alignX: 'spaceBetween', direction: 'inline' })
141
- // → 'justify-content: space-between;'`,
142
- seeAlso: ['makeItResponsive'],
143
- },
144
- {
145
- name: 'extendCss',
146
- kind: 'function',
147
- signature: 'extendCss(base: ExtendCss, override?: ExtendCss): ExtendCss',
148
- summary:
149
- 'Extend a CSS definition (theme block, style descriptor) with overrides — deep-merges nested objects without losing the base. Used by rocketstyle dimension chains to layer dimension-specific CSS over a baseline. The base is not mutated; the result is a new object.',
150
- example: `import { extendCss } from '@pyreon/unistyle'
151
-
152
- const base = { color: 'red', hover: { color: 'darkred' } }
153
- const extended = extendCss(base, { hover: { background: 'pink' } })
154
- // → { color: 'red', hover: { color: 'darkred', background: 'pink' } }`,
155
- seeAlso: ['styles'],
156
- },
157
- {
158
- name: 'stripUnit',
159
- kind: 'function',
160
- signature: 'stripUnit(value: string | number): number',
161
- summary:
162
- 'Strip the unit suffix from a CSS value and return the numeric part (`"16px"` → `16`, `"1.5rem"` → `1.5`). Returns the input unchanged when already a number. Useful for arithmetic on theme values declared as strings (`"16px"`) without manually parsing.',
163
- example: `import { stripUnit } from '@pyreon/unistyle'
164
-
165
- stripUnit('16px') // → 16
166
- stripUnit('1.5rem') // → 1.5
167
- stripUnit(16) // → 16`,
168
- seeAlso: ['value', 'values'],
169
- },
170
- {
171
- name: 'value',
172
- kind: 'function',
173
- signature: 'value(input: PropertyValue, fallback?: PropertyValue): UnitValue',
174
- summary:
175
- 'Parse and validate a single property value into a `UnitValue` shape (`{ value, unit }`). Accepts numbers (treated as pixels), strings with units (`"16px"`, `"1rem"`, `"50%"`), or objects already in `UnitValue` form. Optional `fallback` is returned when the input is invalid. The companion `values()` does the same over an array.',
176
- example: `import { value } from '@pyreon/unistyle'
177
-
178
- value(16) // → { value: 16, unit: 'px' }
179
- value('1.5rem') // → { value: 1.5, unit: 'rem' }
180
- value('50%') // → { value: 50, unit: '%' }
181
- value('garbage', 0) // → { value: 0, unit: 'px' }`,
182
- seeAlso: ['stripUnit', 'values'],
183
- },
184
- ],
185
- gotchas: [
186
- {
187
- label: 'Single source for responsive semantics',
188
- note:
189
- 'Every visual package (`elements`, `rocketstyle`, `coolgrid`, `kinetic`) reads breakpoints / spacing / unit conventions from this package. Override defaults via `enrichTheme()` once at the app root rather than per-component.',
190
- },
191
- {
192
- label: 'CSS property naming',
193
- note:
194
- 'Unistyle uses property-first naming (`borderWidthTop`, `borderColorLeft`) rather than CSS-spec order (`borderTopWidth`, `borderLeftColor`). Stick to the unistyle convention when authoring components — the responsive transformer expects it.',
195
- },
196
- ],
197
- })
@@ -1,15 +0,0 @@
1
- const breakpoints = {
2
- rootSize: 16,
3
- breakpoints: {
4
- xs: 0,
5
- sm: 576,
6
- md: 768,
7
- lg: 992,
8
- xl: 1200,
9
- xxl: 1440,
10
- },
11
- } as const
12
-
13
- export type Breakpoints = typeof breakpoints
14
-
15
- export default breakpoints
@@ -1,43 +0,0 @@
1
- type Css = (strings: TemplateStringsArray, ...values: any[]) => any
2
-
3
- export type CreateMediaQueries = <
4
- B extends Record<string, number>,
5
- R extends number,
6
- C extends Css,
7
- >(props: {
8
- breakpoints: B
9
- rootSize: R
10
- css: C
11
- }) => Record<keyof B, (...args: any[]) => string>
12
-
13
- // Implementation uses Record<string, ...> which is widened from Record<keyof B, ...>;
14
- // the generic constraint on CreateMediaQueries ensures callers get the narrower type.
15
- const createMediaQueries: CreateMediaQueries = ((props: {
16
- breakpoints: Record<string, number>
17
- rootSize: number
18
- css: Css
19
- }) => {
20
- const { breakpoints, rootSize, css } = props
21
-
22
- // Direct for-in + mutation. The prior `Object.keys.reduce` allocated the
23
- // keys array and paid reduce-callback overhead per iteration. Hot at
24
- // PyreonUI mount and on any theme/rootSize change. Ported from
25
- // vitus-labs `e573e6c4`; measured upstream: +15.9%.
26
- const acc: Record<string, (...args: [TemplateStringsArray, ...any[]]) => string> = {}
27
- for (const key in breakpoints) {
28
- const breakpointValue = breakpoints[key]
29
- if (breakpointValue === 0) {
30
- acc[key] = (...args: [TemplateStringsArray, ...any[]]) => css(...args)
31
- } else if (breakpointValue != null) {
32
- const emSize = breakpointValue / rootSize
33
- acc[key] = (...args: [TemplateStringsArray, ...any[]]) => css`
34
- @media only screen and (min-width: ${emSize}em) {
35
- ${css(...args)};
36
- }
37
- `
38
- }
39
- }
40
- return acc
41
- }) as CreateMediaQueries
42
-
43
- export default createMediaQueries
@@ -1,15 +0,0 @@
1
- export type { Breakpoints } from './breakpoints'
2
- export { default as breakpoints } from './breakpoints'
3
- export type { CreateMediaQueries } from './createMediaQueries'
4
- export { default as createMediaQueries } from './createMediaQueries'
5
- export type { MakeItResponsive, MakeItResponsiveStyles } from './makeItResponsive'
6
- export { default as makeItResponsive } from './makeItResponsive'
7
- export type { NormalizeTheme } from './normalizeTheme'
8
- export { default as normalizeTheme } from './normalizeTheme'
9
- export { default as optimizeBreakpointDeltas } from './optimizeBreakpointDeltas'
10
- export type { OptimizeTheme } from './optimizeTheme'
11
- export { default as optimizeTheme } from './optimizeTheme'
12
- export type { SortBreakpoints } from './sortBreakpoints'
13
- export { default as sortBreakpoints } from './sortBreakpoints'
14
- export type { TransformTheme } from './transformTheme'
15
- export { default as transformTheme } from './transformTheme'
@@ -1,223 +0,0 @@
1
- import { isEmpty } from '@pyreon/ui-core'
2
- import type createMediaQueries from './createMediaQueries'
3
- import normalizeTheme from './normalizeTheme'
4
- import optimizeBreakpointDeltas from './optimizeBreakpointDeltas'
5
- import optimizeTheme from './optimizeTheme'
6
- import type sortBreakpoints from './sortBreakpoints'
7
- import transformTheme from './transformTheme'
8
-
9
- type Css = (strings: TemplateStringsArray, ...values: any[]) => any
10
-
11
- /**
12
- * Coerce a styles-callback result to a CSS string for delta optimization.
13
- * Returns null when the engine's result type can't be stringified cleanly
14
- * (e.g. styled-components / Emotion objects whose default toString() yields
15
- * "[object Object]") — caller falls back to the unoptimized path.
16
- *
17
- * Styler's CSSResult provides toString() that resolves with empty props,
18
- * so any function interpolation that needs render-time props must come from
19
- * the styles-callback closure (theme is destructured at call time, not
20
- * resolved later). Verified across the project's styles callbacks.
21
- */
22
- const stringifyResult = (result: unknown): string | null => {
23
- if (result == null) return ''
24
- if (typeof result === 'string') return result
25
- // CSSResult duck-type fast path: has `strings` (TemplateStringsArray) and
26
- // `values`. We know its toString() resolves to clean CSS, so we can skip
27
- // the "[object Foo]" validation for the common path.
28
- if (typeof result === 'object' && 'strings' in result && 'values' in result) {
29
- return String(result)
30
- }
31
- // Foreign engine result — coerce and validate. Default
32
- // Object.prototype.toString → "[object Foo]" → bail out so caller can fall
33
- // back to the unoptimized path.
34
- const text = String(result)
35
- return text.includes('[object ') ? null : text
36
- }
37
-
38
- type CustomTheme = Record<string, unknown>
39
-
40
- type Theme = Partial<{
41
- rootSize: number
42
- breakpoints: Record<string, number>
43
- __PYREON__: Partial<{
44
- media: ReturnType<typeof createMediaQueries>
45
- sortedBreakpoints: ReturnType<typeof sortBreakpoints>
46
- }>
47
- }> &
48
- CustomTheme
49
-
50
- export type MakeItResponsiveStyles<T extends Partial<Record<string, any>> = any> = ({
51
- theme,
52
- css,
53
- rootSize,
54
- globalTheme,
55
- }: {
56
- theme: T
57
- css: Css
58
- rootSize?: number | undefined
59
- globalTheme?: Record<string, any> | undefined
60
- }) => ReturnType<typeof css> | string | any
61
-
62
- export type MakeItResponsive = ({
63
- theme,
64
- key,
65
- css,
66
- styles,
67
- normalize,
68
- }: {
69
- theme?: CustomTheme
70
- key?: string
71
- css: any
72
- styles: MakeItResponsiveStyles
73
- normalize?: boolean
74
- }) => (props: { theme?: Theme; [prop: string]: any }) => any
75
-
76
- /**
77
- * Per-internal-theme cache:
78
- * - `optimized`: the per-breakpoint theme object (`{ xs: {...}, md: {...} }`)
79
- * after `normalize → transform → optimize`. Reused as long as the same
80
- * `sortedBreakpoints` reference is passed in.
81
- * - `rendered`: memoized FINAL output (array of media-wrapped CSSResults),
82
- * keyed by the outer `theme` reference. Hit when the same internal theme
83
- * AND the same outer theme render again — which is the common case when
84
- * the provider value is stable. Avoids re-running renderStyles +
85
- * optimizeBreakpointDeltas on every parent re-render.
86
- */
87
- interface ThemeCacheEntry {
88
- breakpoints: unknown
89
- optimized: Record<string, Record<string, unknown>>
90
- rendered?: WeakMap<object, unknown[]> | undefined
91
- }
92
-
93
- const themeCache = new WeakMap<object, ThemeCacheEntry>()
94
-
95
- /**
96
- * Core responsive engine used by every styled component in the system.
97
- *
98
- * Returns a styled-components interpolation function that:
99
- * 1. Reads the component's theme prop (via `key` or direct `theme`)
100
- * 2. Without breakpoints → renders plain CSS
101
- * 3. With breakpoints → normalizes, transforms (property-per-breakpoint →
102
- * breakpoint-per-property), optimizes (deduplicates identical breakpoints),
103
- * deltas the per-breakpoint output against the mobile-first cascade
104
- * (drops re-emitted unchanged declarations), and wraps each non-empty
105
- * breakpoint's deltas in the appropriate `@media` query. Falls back to
106
- * the unoptimized path if any breakpoint's render result can't be
107
- * cleanly stringified.
108
- */
109
- const makeItResponsive: MakeItResponsive =
110
- ({ theme: customTheme, key = '', css, styles, normalize = true }) =>
111
- ({ theme = {}, ...props }) => {
112
- const internalTheme = customTheme || props[key]
113
-
114
- // if no theme is defined, return empty object
115
- if (isEmpty(internalTheme)) return ''
116
-
117
- const { rootSize, breakpoints, __PYREON__, ...restTheme } = theme as Theme
118
-
119
- const renderStyles = (styleTheme: Record<string, unknown>): ReturnType<typeof styles> =>
120
- styles({ theme: styleTheme, css, rootSize, globalTheme: restTheme })
121
-
122
- // if there are no breakpoints, return just standard css
123
- if (isEmpty(breakpoints) || isEmpty(__PYREON__)) {
124
- return css`
125
- ${renderStyles(internalTheme)}
126
- `
127
- }
128
-
129
- // isEmpty guard above ensures __PYREON__ is defined here
130
- const { media, sortedBreakpoints } = __PYREON__ as NonNullable<typeof __PYREON__>
131
-
132
- let optimizedTheme: Record<string, Record<string, unknown>>
133
- const entry = themeCache.get(internalTheme)
134
- const breakpointsMatch = entry?.breakpoints === sortedBreakpoints
135
-
136
- // Full-render cache: same internal theme + same outer theme → return
137
- // the previous render's output verbatim. CSSResult instances are
138
- // immutable so reusing them is safe.
139
- if (entry && breakpointsMatch && entry.rendered) {
140
- const memoized = entry.rendered.get(theme as object)
141
- if (memoized) return memoized
142
- }
143
-
144
- if (entry && breakpointsMatch) {
145
- optimizedTheme = entry.optimized
146
- } else {
147
- let helperTheme = internalTheme
148
-
149
- if (normalize) {
150
- helperTheme = normalizeTheme({
151
- theme: internalTheme,
152
- breakpoints: sortedBreakpoints ?? [],
153
- })
154
- }
155
-
156
- const transformedTheme = transformTheme({
157
- theme: helperTheme,
158
- breakpoints: sortedBreakpoints ?? [],
159
- })
160
-
161
- optimizedTheme = optimizeTheme({
162
- theme: transformedTheme,
163
- breakpoints: sortedBreakpoints ?? [],
164
- })
165
-
166
- themeCache.set(internalTheme, {
167
- breakpoints: sortedBreakpoints,
168
- optimized: optimizedTheme,
169
- // Preserve any pre-existing rendered cache when re-entering with a
170
- // changed sortedBreakpoints reference — usually unreachable because
171
- // breakpoints come from a stable provider value, but the explicit
172
- // handling avoids a memory cliff in tests / HMR.
173
- rendered: entry?.rendered,
174
- })
175
- }
176
-
177
- const bps = sortedBreakpoints ?? []
178
-
179
- // Resolve each per-breakpoint render to a string so the delta optimizer
180
- // can diff at the property level. If any breakpoint's result can't be
181
- // cleanly stringified (foreign engine result), fall back to the original
182
- // unoptimized path that lets the engine resolve interpolations itself.
183
- const renderedTexts: (string | null)[] = bps.map((item: string) => {
184
- const breakpointTheme = optimizedTheme[item]
185
- if (!breakpointTheme || !media) return ''
186
- return stringifyResult(renderStyles(breakpointTheme))
187
- })
188
-
189
- const canOptimize = renderedTexts.every((t) => t !== null)
190
- let result: unknown[]
191
- if (canOptimize) {
192
- const deltas = optimizeBreakpointDeltas(renderedTexts as string[])
193
- result = bps.map((item: string, i: number) => {
194
- const cssText = deltas[i]
195
- if (!cssText || !media) return ''
196
- return (media as Record<string, any>)[item]`${cssText}`
197
- })
198
- } else {
199
- result = bps.map((item: string) => {
200
- const breakpointTheme = optimizedTheme[item]
201
- if (!breakpointTheme || !media) return ''
202
- const r = renderStyles(breakpointTheme)
203
- return (media as Record<string, any>)[item]`
204
- ${r};
205
- `
206
- })
207
- }
208
-
209
- // Memoize the final rendered output by outer theme reference. Stable
210
- // theme + stable internal theme → future renders return immediately.
211
- // Invariant: by this point themeCache always has an entry for
212
- // internalTheme — earlier paths either hit the rendered-cache and
213
- // returned, or wrote one via themeCache.set above.
214
- const cacheEntry = themeCache.get(internalTheme)
215
- if (cacheEntry) {
216
- if (!cacheEntry.rendered) cacheEntry.rendered = new WeakMap()
217
- cacheEntry.rendered.set(theme as object, result)
218
- }
219
-
220
- return result
221
- }
222
-
223
- export default makeItResponsive
@@ -1,79 +0,0 @@
1
- type AssignToBreakpointKey = (
2
- breakpoints: string[],
3
- ) => (
4
- valueFn: (breakpoint: string, i: number, bps: string[], result: Record<string, unknown>) => void,
5
- ) => Record<string, unknown>
6
-
7
- const assignToBreakpointKey: AssignToBreakpointKey = (breakpoints) => (valueFn) => {
8
- const result: Record<string, unknown> = {}
9
- breakpoints.forEach((item, i) => {
10
- result[item] = valueFn(item, i, breakpoints, result)
11
- })
12
- return result
13
- }
14
-
15
- const handleArrayCb = (arr: (string | number)[]) => (_: unknown, i: number) => {
16
- const currentValue = arr[i]
17
- const lastValue = arr[arr.length - 1]
18
- return currentValue ?? lastValue
19
- }
20
-
21
- const handleObjectCb =
22
- (obj: Record<string, unknown>) =>
23
- (bp: string, i: number, bps: string[], res: Record<string, unknown>) => {
24
- const currentValue = obj[bp]
25
- const prevBp = bps[i - 1]
26
- const previousValue = prevBp != null ? res[prevBp] : undefined
27
- if (currentValue != null) return currentValue
28
- return previousValue
29
- }
30
-
31
- const handleValueCb = (value: unknown) => () => value
32
-
33
- // for-in early-exit avoids the `Object.values(props)` array allocation
34
- // that the prior `.some()` paid on every theme normalization decision.
35
- // Fires once per per-breakpoint theme transform; the early `return true`
36
- // is hit by any responsive token, so most calls bail out quickly. Ported
37
- // from vitus-labs `e573e6c4`; measured upstream: +20.3%.
38
- const shouldNormalize = (props: Record<string, any>) => {
39
- for (const key in props) {
40
- const item = props[key]
41
- if (typeof item === 'object' || Array.isArray(item)) return true
42
- }
43
- return false
44
- }
45
-
46
- export type NormalizeTheme = ({
47
- theme,
48
- breakpoints,
49
- }: {
50
- theme: Record<string, unknown>
51
- breakpoints: string[]
52
- }) => Record<string, unknown>
53
-
54
- const normalizeTheme: NormalizeTheme = ({ theme, breakpoints }) => {
55
- if (!shouldNormalize(theme)) return theme
56
-
57
- const getBpValues = assignToBreakpointKey(breakpoints)
58
- const result: Record<string, unknown> = {}
59
-
60
- // for-in instead of Object.entries.forEach — avoids the entries-tuple
61
- // array allocation per theme normalization (one outer alloc + one inner
62
- // [k,v] tuple per property dropped). Ported from vitus-labs `e573e6c4`.
63
- for (const key in theme) {
64
- const value = theme[key]
65
- if (value == null) continue
66
-
67
- if (Array.isArray(value)) {
68
- result[key] = getBpValues(handleArrayCb(value as (string | number)[]))
69
- } else if (typeof value === 'object') {
70
- result[key] = getBpValues(handleObjectCb(value as Record<string, any>))
71
- } else {
72
- result[key] = getBpValues(handleValueCb(value))
73
- }
74
- }
75
-
76
- return result
77
- }
78
-
79
- export default normalizeTheme