@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
@@ -1,190 +0,0 @@
1
- /**
2
- * Mobile-first cascade optimizer.
3
- *
4
- * Given an ordered array of CSS strings (one per breakpoint, smallest first),
5
- * returns a parallel array where each non-base breakpoint contains only the
6
- * declarations that DIFFER from the cumulative cascade so far. This relies on
7
- * mobile-first `@media (min-width: …)` semantics: properties set at smaller
8
- * breakpoints inherit at larger ones, so re-emitting an unchanged property is
9
- * pure byte waste.
10
- *
11
- * Example:
12
- * ["color: red; padding: 0;", "color: red; padding: 1rem;"]
13
- * → ["color: red; padding: 0;", "padding: 1rem;"]
14
- *
15
- * Top-level declarations are diffed by `prop:value`. Selector blocks
16
- * (`&:hover { … }`, `@supports { … }`) are treated as opaque and deduped by
17
- * exact text. Anything inside parens or quoted strings is skipped over so
18
- * `linear-gradient(red 0%, blue 100%)` and `content: ";"` parse correctly.
19
- *
20
- * Limitations:
21
- * - shorthand/longhand interaction is not modeled. If breakpoint A sets
22
- * `padding: 1rem` and breakpoint B sets `padding-top: 0`, both are kept
23
- * (they have different `prop` keys). If A sets `padding-top: 1rem` and B
24
- * sets `padding: 1rem`, B's `padding` is emitted because the cascade map
25
- * has no entry for `padding`. This is correct: B's shorthand RESETS sides
26
- * A didn't touch, so dropping it would change behaviour.
27
- * - Nested blocks are deduped only by exact textual match. Two equivalent
28
- * blocks with different whitespace would both be emitted.
29
- */
30
-
31
- interface DeclEntry {
32
- kind: 'decl'
33
- prop: string
34
- value: string
35
- raw: string // canonical "prop: value;" form
36
- }
37
-
38
- interface BlockEntry {
39
- kind: 'block'
40
- raw: string // entire "selector { body }" or stray block
41
- }
42
-
43
- type Entry = DeclEntry | BlockEntry
44
-
45
- /** Parse a CSS string into top-level declarations and opaque blocks. */
46
- const parse = (css: string): Entry[] => {
47
- const entries: Entry[] = []
48
- const len = css.length
49
-
50
- let depth = 0
51
- let parenDepth = 0
52
- let quote = 0 // charCode of active quote (0 if none)
53
- let segmentStart = 0
54
-
55
- const pushSegment = (rawSegment: string) => {
56
- const trimmed = rawSegment.trim()
57
- if (!trimmed) return
58
- // pushSegment is only reached for segments that ended with a top-level
59
- // ";" — full "selector { ... }" blocks are captured separately by the
60
- // brace walker, so this path always sees declarations (or malformed
61
- // declaration-shaped fragments).
62
- const text = trimmed.endsWith(';') ? trimmed.slice(0, -1) : trimmed
63
- const colonIdx = text.indexOf(':')
64
- if (colonIdx <= 0) {
65
- // No ":" or starts with ":" → not a parseable declaration; keep raw
66
- entries.push({ kind: 'block', raw: `${text};` })
67
- return
68
- }
69
- const prop = text.slice(0, colonIdx).trim()
70
- const value = text.slice(colonIdx + 1).trim()
71
- if (!prop || !value) {
72
- entries.push({ kind: 'block', raw: `${text};` })
73
- return
74
- }
75
- entries.push({
76
- kind: 'decl',
77
- prop,
78
- value,
79
- raw: `${prop}: ${value};`,
80
- })
81
- }
82
-
83
- for (let i = 0; i < len; i++) {
84
- const code = css.charCodeAt(i)
85
-
86
- // Inside a quoted string — skip until matching quote (ignoring escapes)
87
- if (quote !== 0) {
88
- if (code === 92 /* \ */) {
89
- i++ // skip the next character
90
- } else if (code === quote) {
91
- quote = 0
92
- }
93
- continue
94
- }
95
-
96
- // Quote start
97
- if (code === 34 /* " */ || code === 39 /* ' */) {
98
- quote = code
99
- continue
100
- }
101
-
102
- // Parens — content (e.g. linear-gradient args) shouldn't be interpreted
103
- if (code === 40 /* ( */) {
104
- parenDepth++
105
- continue
106
- }
107
- if (code === 41 /* ) */) {
108
- if (parenDepth > 0) parenDepth--
109
- continue
110
- }
111
- if (parenDepth > 0) continue
112
-
113
- if (code === 123 /* { */) {
114
- depth++
115
- continue
116
- }
117
- if (code === 125 /* } */) {
118
- depth--
119
- if (depth === 0) {
120
- // End of a top-level block — capture from segmentStart..i (inclusive)
121
- const raw = css.slice(segmentStart, i + 1).trim()
122
- if (raw) entries.push({ kind: 'block', raw })
123
- segmentStart = i + 1
124
- }
125
- continue
126
- }
127
-
128
- if (depth === 0 && code === 59 /* ; */) {
129
- pushSegment(css.slice(segmentStart, i))
130
- segmentStart = i + 1
131
- }
132
- }
133
-
134
- // Trailing segment (no terminating semicolon)
135
- if (segmentStart < len) {
136
- const trailing = css.slice(segmentStart).trim()
137
- if (trailing) {
138
- if (depth > 0) {
139
- // Unbalanced braces — keep the rest as opaque so output isn't lossy
140
- entries.push({ kind: 'block', raw: trailing })
141
- } else {
142
- pushSegment(trailing)
143
- }
144
- }
145
- }
146
-
147
- return entries
148
- }
149
-
150
- /**
151
- * Apply the mobile-first cascade diff. The first entry passes through
152
- * unchanged; subsequent entries are pruned to the delta vs. the running
153
- * cascade (declarations by prop, blocks by exact text match).
154
- */
155
- export const optimizeBreakpointDeltas = (cssStrings: string[]): string[] => {
156
- if (cssStrings.length <= 1) return cssStrings
157
-
158
- const cascadeDecl = new Map<string, string>()
159
- const cascadeBlocks = new Set<string>()
160
- const out: string[] = new Array(cssStrings.length)
161
-
162
- for (let i = 0; i < cssStrings.length; i++) {
163
- const css = cssStrings[i]
164
- if (!css) {
165
- out[i] = ''
166
- continue
167
- }
168
-
169
- const entries = parse(css)
170
- const kept: string[] = []
171
-
172
- for (const e of entries) {
173
- if (e.kind === 'decl') {
174
- if (cascadeDecl.get(e.prop) !== e.value) {
175
- kept.push(e.raw)
176
- cascadeDecl.set(e.prop, e.value)
177
- }
178
- } else if (!cascadeBlocks.has(e.raw)) {
179
- kept.push(e.raw)
180
- cascadeBlocks.add(e.raw)
181
- }
182
- }
183
-
184
- out[i] = kept.join(' ')
185
- }
186
-
187
- return out
188
- }
189
-
190
- export default optimizeBreakpointDeltas
@@ -1,60 +0,0 @@
1
- export type OptimizeTheme = ({
2
- theme,
3
- breakpoints,
4
- }: {
5
- theme: Record<string, Record<string, unknown>>
6
- breakpoints: string[]
7
- }) => Record<string, Record<string, unknown>>
8
-
9
- const shallowEqual = (
10
- a: Record<string, unknown> | undefined,
11
- b: Record<string, unknown> | undefined,
12
- ): boolean => {
13
- if (a === b) return true
14
- if (!a || !b) return false
15
- // for-in + counting avoids the two `Object.keys` array allocations the
16
- // prior implementation paid on every breakpoint optimization step.
17
- // Ported from vitus-labs `e573e6c4`; measured upstream: +4.0% on the
18
- // EQUAL hot path (the common case in steady-state renders).
19
- let aCount = 0
20
- for (const key in a) {
21
- aCount++
22
- if (a[key] !== b[key]) return false
23
- }
24
- let bCount = 0
25
- for (const _ in b) bCount++
26
- return aCount === bCount
27
- }
28
-
29
- /**
30
- * Removes breakpoints whose full-object styles are identical to the
31
- * previous one. Simple all-or-nothing: if ANY property differs from
32
- * the previous breakpoint, emit the ENTIRE current breakpoint.
33
- *
34
- * This matches the reference implementation (vitus-labs/ui-system) and
35
- * the original monorepo-migration version (commit 2b7c5876). Previous
36
- * "optimizations" (PRs #159, #208) that tried per-property diffing
37
- * broke responsive styles in subtle ways — shorthand/longhand CSS
38
- * property interactions, properties that depend on each other,
39
- * properties that need to be emitted together to cascade correctly.
40
- *
41
- * The all-or-nothing approach is the correct level of deduplication —
42
- * we skip the breakpoint when it's entirely redundant, and let the
43
- * browser's CSS cascade handle the rest.
44
- */
45
- const optimizeTheme: OptimizeTheme = ({ theme, breakpoints }) => {
46
- const result: Record<string, Record<string, unknown>> = {}
47
-
48
- for (let i = 0; i < breakpoints.length; i++) {
49
- const key = breakpoints[i] as string
50
- const previousBreakpoint = breakpoints[i - 1] as string
51
- const current = theme[key]
52
- if (current && (i === 0 || !shallowEqual(theme[previousBreakpoint], current))) {
53
- result[key] = current
54
- }
55
- }
56
-
57
- return result
58
- }
59
-
60
- export default optimizeTheme
@@ -1,10 +0,0 @@
1
- export type SortBreakpoints = <T extends Record<string, number>>(breakpoints: T) => (keyof T)[]
2
-
3
- const sortBreakpoints: SortBreakpoints = (breakpoints) => {
4
- const result = Object.keys(breakpoints).sort(
5
- (a, b) => (breakpoints[a] ?? 0) - (breakpoints[b] ?? 0),
6
- )
7
- return result
8
- }
9
-
10
- export default sortBreakpoints
@@ -1,54 +0,0 @@
1
- import { isEmpty, set } from '@pyreon/ui-core'
2
-
3
- const removeUnexpectedKeys = (obj: Record<string, unknown>, keys: string[]) => {
4
- const result: Record<string, unknown> = {}
5
- keys.forEach((bp) => {
6
- const value = obj[bp]
7
- if (value) {
8
- result[bp] = value
9
- }
10
- })
11
- return result
12
- }
13
-
14
- export type TransformTheme = ({
15
- theme,
16
- breakpoints,
17
- }: {
18
- theme: Record<string, unknown>
19
- breakpoints: string[]
20
- }) => any
21
-
22
- const transformTheme: TransformTheme = ({ theme, breakpoints }) => {
23
- const result = {}
24
-
25
- if (isEmpty(theme) || isEmpty(breakpoints)) return result
26
-
27
- // for-in + nested for-in avoids the two `Object.entries(...)` array
28
- // allocations (outer + inner per object value) the prior forEach paid.
29
- // Same for `value.forEach((child, i) => ...)` → indexed for-loop.
30
- // Ported from vitus-labs `e573e6c4`.
31
- for (const key in theme) {
32
- const value = theme[key]
33
- if (Array.isArray(value) && value.length > 0) {
34
- for (let i = 0; i < value.length; i++) {
35
- const indexBreakpoint = breakpoints[i]
36
- if (indexBreakpoint == null) continue
37
- set(result, [indexBreakpoint, key], value[i])
38
- }
39
- } else if (typeof value === 'object' && value !== null) {
40
- const obj = value as Record<string, unknown>
41
- for (const childKey in obj) {
42
- set(result, [childKey, key], obj[childKey])
43
- }
44
- } else if (value != null) {
45
- const firstBreakpoint = breakpoints[0]
46
- if (firstBreakpoint == null) continue
47
- set(result, [firstBreakpoint, key], value)
48
- }
49
- }
50
-
51
- return removeUnexpectedKeys(result, breakpoints)
52
- }
53
-
54
- export default transformTheme
@@ -1,62 +0,0 @@
1
- import { isEmpty } from '@pyreon/ui-core'
2
-
3
- export type AlignContentDirectionKeys = keyof typeof ALIGN_CONTENT_DIRECTION
4
- export type AlignContentAlignXKeys = keyof typeof ALIGN_CONTENT_MAP_X
5
- export type AlignContentAlignYKeys = keyof typeof ALIGN_CONTENT_MAP_Y
6
-
7
- const ALIGN_CONTENT_MAP_SHARED = {
8
- center: 'center',
9
- spaceBetween: 'space-between',
10
- spaceAround: 'space-around',
11
- block: 'stretch',
12
- }
13
-
14
- export const ALIGN_CONTENT_MAP_X = {
15
- left: 'flex-start',
16
- right: 'flex-end',
17
- ...ALIGN_CONTENT_MAP_SHARED,
18
- } as const
19
-
20
- export const ALIGN_CONTENT_MAP_Y = {
21
- top: 'flex-start',
22
- bottom: 'flex-end',
23
- ...ALIGN_CONTENT_MAP_SHARED,
24
- } as const
25
-
26
- export const ALIGN_CONTENT_DIRECTION = {
27
- inline: 'row',
28
- reverseInline: 'row-reverse',
29
- rows: 'column',
30
- reverseRows: 'column-reverse',
31
- } as const
32
-
33
- export type AlignContent = ({
34
- direction,
35
- alignX,
36
- alignY,
37
- }: {
38
- direction: AlignContentDirectionKeys
39
- alignX: AlignContentAlignXKeys
40
- alignY: AlignContentAlignYKeys
41
- }) => string | null
42
-
43
- const alignContent: AlignContent = (attrs) => {
44
- const { direction, alignX, alignY } = attrs
45
-
46
- if (isEmpty(attrs) || !direction || !alignX || !alignY) {
47
- return null
48
- }
49
-
50
- // Direct comparisons avoid the per-call 2-element array allocation that
51
- // `['inline', 'reverseInline'].includes(direction)` paid. Hot path: fires
52
- // for every styled component with a `direction` prop. Ported from
53
- // vitus-labs `e573e6c4`.
54
- const isReverted = direction === 'inline' || direction === 'reverseInline'
55
- const dir = ALIGN_CONTENT_DIRECTION[direction]
56
- const x = ALIGN_CONTENT_MAP_X[alignX]
57
- const y = ALIGN_CONTENT_MAP_Y[alignY]
58
-
59
- return `flex-direction: ${dir}; align-items: ${isReverted ? y : x}; justify-content: ${isReverted ? x : y};`
60
- }
61
-
62
- export default alignContent
@@ -1,26 +0,0 @@
1
- export type ExtendCss = (
2
- styles:
3
- | ((css: (strings: TemplateStringsArray, ...values: any[]) => string) => string)
4
- | string
5
- | null
6
- | undefined,
7
- ) => string
8
-
9
- const simpleCss = (strings: TemplateStringsArray, ...values: any[]): string => {
10
- let result = ''
11
- for (let i = 0; i < strings.length; i++) {
12
- result += strings[i]
13
- if (i < values.length) result += String(values[i] ?? '')
14
- }
15
- return result
16
- }
17
-
18
- const extendCss: ExtendCss = (styles) => {
19
- if (!styles) return ''
20
- if (typeof styles === 'function') {
21
- return styles(simpleCss)
22
- }
23
- return styles
24
- }
25
-
26
- export default extendCss
@@ -1,16 +0,0 @@
1
- export type {
2
- AlignContent,
3
- AlignContentAlignXKeys,
4
- AlignContentAlignYKeys,
5
- AlignContentDirectionKeys,
6
- } from './alignContent'
7
- export {
8
- ALIGN_CONTENT_DIRECTION,
9
- ALIGN_CONTENT_MAP_X,
10
- ALIGN_CONTENT_MAP_Y,
11
- default as alignContent,
12
- } from './alignContent'
13
- export type { ExtendCss } from './extendCss'
14
- export { default as extendCss } from './extendCss'
15
- export type { ITheme, Styles, StylesTheme } from './styles'
16
- export { default as styles } from './styles'
@@ -1,89 +0,0 @@
1
- import { value } from '../../units'
2
-
3
- type PropertyValue = string | number | null | undefined
4
- type PV = PropertyValue
5
-
6
- const isValidValue = (v: unknown) => !!v || v === 0
7
-
8
- type CornerValues = {
9
- full: PV
10
- top: PV
11
- bottom: PV
12
- left: PV
13
- right: PV
14
- topLeft: PV
15
- topRight: PV
16
- bottomLeft: PV
17
- bottomRight: PV
18
- }
19
-
20
- const hasAnyValue = (v: CornerValues) =>
21
- isValidValue(v.full) ||
22
- isValidValue(v.top) ||
23
- isValidValue(v.bottom) ||
24
- isValidValue(v.left) ||
25
- isValidValue(v.right) ||
26
- isValidValue(v.topLeft) ||
27
- isValidValue(v.topRight) ||
28
- isValidValue(v.bottomLeft) ||
29
- isValidValue(v.bottomRight)
30
-
31
- const resolveCorners = (v: CornerValues) => {
32
- const corners: PV[] = [v.full, v.full, v.full, v.full]
33
- if (isValidValue(v.top)) {
34
- corners[0] = v.top
35
- corners[1] = v.top
36
- }
37
- if (isValidValue(v.bottom)) {
38
- corners[2] = v.bottom
39
- corners[3] = v.bottom
40
- }
41
- if (isValidValue(v.left)) {
42
- corners[0] = v.left
43
- corners[3] = v.left
44
- }
45
- if (isValidValue(v.right)) {
46
- corners[1] = v.right
47
- corners[2] = v.right
48
- }
49
- if (isValidValue(v.topLeft)) corners[0] = v.topLeft
50
- if (isValidValue(v.topRight)) corners[1] = v.topRight
51
- if (isValidValue(v.bottomRight)) corners[2] = v.bottomRight
52
- if (isValidValue(v.bottomLeft)) corners[3] = v.bottomLeft
53
- return corners
54
- }
55
-
56
- const formatShorthand = (corners: PV[], calc: (p: PV) => any) => {
57
- const [tl, tr, br, bl] = corners
58
- if (corners.every((val, _, arr) => val === arr[0])) return `border-radius: ${calc(tl)};`
59
- if (tl === br && tr === bl) return `border-radius: ${calc(tl)} ${calc(tr)};`
60
- if (tl && tr === bl && br) return `border-radius: ${calc(tl)} ${calc(tr)} ${calc(br)};`
61
- return `border-radius: ${calc(tl)} ${calc(tr)} ${calc(br)} ${calc(bl)};`
62
- }
63
-
64
- const CORNER_CSS = [
65
- 'border-top-left-radius',
66
- 'border-top-right-radius',
67
- 'border-bottom-right-radius',
68
- 'border-bottom-left-radius',
69
- ] as const
70
-
71
- const formatIndividual = (corners: PV[], calc: (p: PV) => any) => {
72
- let output = ''
73
- for (let i = 0; i < corners.length; i++) {
74
- if (isValidValue(corners[i])) output += `${CORNER_CSS[i]}: ${calc(corners[i])};`
75
- }
76
- return output
77
- }
78
-
79
- export type BorderRadius = (rootSize?: number) => (props: CornerValues) => string | null
80
-
81
- const borderRadius: BorderRadius = (rootSize) => (props) => {
82
- if (!hasAnyValue(props)) return null
83
- const calc = (param: PV) => value(param, rootSize)
84
- const corners = resolveCorners(props)
85
- if (corners.every((val) => isValidValue(val))) return formatShorthand(corners, calc)
86
- return formatIndividual(corners, calc)
87
- }
88
-
89
- export default borderRadius
@@ -1,108 +0,0 @@
1
- import { value } from '../../units'
2
-
3
- type CssUnits =
4
- | 'px'
5
- | 'rem'
6
- | '%'
7
- | 'em'
8
- | 'ex'
9
- | 'cm'
10
- | 'mm'
11
- | 'in'
12
- | 'pt'
13
- | 'pc'
14
- | 'ch'
15
- | 'vh'
16
- | 'vw'
17
- | 'vmin'
18
- | 'vmax'
19
-
20
- const isValidValue = (v: unknown) => !!v || v === 0
21
-
22
- type Property = 'inset' | 'margin' | 'padding' | 'border-width' | 'border-style' | 'border-color'
23
- type Value = string | number | null | undefined
24
- type Side = 'top' | 'bottom' | 'left' | 'right'
25
-
26
- type EdgeValues = {
27
- full: Value
28
- x: Value
29
- y: Value
30
- top: Value
31
- left: Value
32
- right: Value
33
- bottom: Value
34
- }
35
-
36
- type Definitions = Record<Property, { unit?: CssUnits; edgeCss: (side: Side) => string }>
37
-
38
- const definitions: Definitions = {
39
- inset: { unit: 'rem', edgeCss: (side) => side },
40
- margin: { unit: 'rem', edgeCss: (side) => `margin-${side}` },
41
- padding: { unit: 'rem', edgeCss: (side) => `padding-${side}` },
42
- 'border-width': { unit: 'px', edgeCss: (side) => `border-${side}-width` },
43
- 'border-style': { edgeCss: (side) => `border-${side}-style` },
44
- 'border-color': { edgeCss: (side) => `border-${side}-color` },
45
- }
46
-
47
- const hasAnyValue = (vals: EdgeValues) =>
48
- isValidValue(vals.top) ||
49
- isValidValue(vals.bottom) ||
50
- isValidValue(vals.left) ||
51
- isValidValue(vals.right) ||
52
- isValidValue(vals.x) ||
53
- isValidValue(vals.y) ||
54
- isValidValue(vals.full)
55
-
56
- const resolveSides = ({ full, x, y, top, left, right, bottom }: EdgeValues) => {
57
- const sides: Value[] = [full, full, full, full]
58
- if (isValidValue(x)) {
59
- sides[1] = x
60
- sides[3] = x
61
- }
62
- if (isValidValue(y)) {
63
- sides[0] = y
64
- sides[2] = y
65
- }
66
- if (isValidValue(top)) sides[0] = top
67
- if (isValidValue(right)) sides[1] = right
68
- if (isValidValue(bottom)) sides[2] = bottom
69
- if (isValidValue(left)) sides[3] = left
70
- return sides
71
- }
72
-
73
- const formatShorthand = (property: Property, sides: Value[], calc: (v: Value) => Value) => {
74
- const [t, r, b, l] = sides
75
- if (sides.every((val, _, arr) => val === arr[0])) return `${property}: ${calc(t)};`
76
- if (t === b && r === l) return `${property}: ${calc(t)} ${calc(r)};`
77
- if (t && r === l && b) return `${property}: ${calc(t)} ${calc(r)} ${calc(b)};`
78
- return `${property}: ${calc(t)} ${calc(r)} ${calc(b)} ${calc(l)};`
79
- }
80
-
81
- const formatIndividual = (
82
- sides: Value[],
83
- edgeCss: (side: Side) => string,
84
- calc: (v: Value) => Value,
85
- ) => {
86
- const [t, r, b, l] = sides
87
- let output = ''
88
- if (isValidValue(t)) output += `${edgeCss('top')}: ${calc(t)};`
89
- if (isValidValue(b)) output += `${edgeCss('bottom')}: ${calc(b)};`
90
- if (isValidValue(l)) output += `${edgeCss('left')}: ${calc(l)};`
91
- if (isValidValue(r)) output += `${edgeCss('right')}: ${calc(r)};`
92
- return output
93
- }
94
-
95
- export type Edge = (rootSize?: number) => (property: Property, values: EdgeValues) => string | null
96
-
97
- const edge: Edge =
98
- (rootSize = 16) =>
99
- (property, values) => {
100
- if (!hasAnyValue(values)) return null
101
- const { unit, edgeCss } = definitions[property]
102
- const calc = (param: Value) => (unit ? value(param, rootSize, unit) : param)
103
- const sides = resolveSides(values)
104
- if (sides.every((val) => isValidValue(val))) return formatShorthand(property, sides, calc)
105
- return formatIndividual(sides, edgeCss, calc)
106
- }
107
-
108
- export default edge
@@ -1,4 +0,0 @@
1
- export type { BorderRadius } from './borderRadius'
2
- export { default as borderRadius } from './borderRadius'
3
- export type { Edge } from './edge'
4
- export { default as edge } from './edge'
@@ -1,3 +0,0 @@
1
- const camelToKebab = (s: string): string => s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)
2
-
3
- export default camelToKebab