@pyreon/unistyle 0.11.1 → 0.11.3

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 (43) hide show
  1. package/package.json +8 -7
  2. package/src/__tests__/alignContent.test.ts +121 -0
  3. package/src/__tests__/borderRadius.test.ts +125 -0
  4. package/src/__tests__/camelToKebab.test.ts +44 -0
  5. package/src/__tests__/context.test.ts +147 -0
  6. package/src/__tests__/createMediaQueries.test.ts +98 -0
  7. package/src/__tests__/edge.test.ts +164 -0
  8. package/src/__tests__/enrichTheme.test.ts +56 -0
  9. package/src/__tests__/extendCss.test.ts +45 -0
  10. package/src/__tests__/index.test.ts +79 -0
  11. package/src/__tests__/makeItResponsive.test.ts +171 -0
  12. package/src/__tests__/processDescriptor.test.ts +320 -0
  13. package/src/__tests__/responsive.test.ts +177 -0
  14. package/src/__tests__/styles.test.ts +119 -0
  15. package/src/__tests__/units.test.ts +134 -0
  16. package/src/context.tsx +34 -0
  17. package/src/enrichTheme.ts +42 -0
  18. package/src/index.ts +89 -0
  19. package/src/responsive/breakpoints.ts +15 -0
  20. package/src/responsive/createMediaQueries.ts +43 -0
  21. package/src/responsive/index.ts +14 -0
  22. package/src/responsive/makeItResponsive.ts +118 -0
  23. package/src/responsive/normalizeTheme.ts +65 -0
  24. package/src/responsive/optimizeTheme.ts +39 -0
  25. package/src/responsive/sortBreakpoints.ts +10 -0
  26. package/src/responsive/transformTheme.ts +48 -0
  27. package/src/styles/alignContent.ts +58 -0
  28. package/src/styles/extendCss.ts +26 -0
  29. package/src/styles/index.ts +16 -0
  30. package/src/styles/shorthands/borderRadius.ts +89 -0
  31. package/src/styles/shorthands/edge.ts +108 -0
  32. package/src/styles/shorthands/index.ts +4 -0
  33. package/src/styles/styles/camelToKebab.ts +3 -0
  34. package/src/styles/styles/index.ts +33 -0
  35. package/src/styles/styles/processDescriptor.ts +100 -0
  36. package/src/styles/styles/propertyMap.ts +436 -0
  37. package/src/styles/styles/types.ts +366 -0
  38. package/src/styles/styles/utils.ts +62 -0
  39. package/src/types.ts +175 -0
  40. package/src/units/index.ts +6 -0
  41. package/src/units/stripUnit.ts +25 -0
  42. package/src/units/value.ts +47 -0
  43. package/src/units/values.ts +40 -0
@@ -0,0 +1,58 @@
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
+ const isReverted = ["inline", "reverseInline"].includes(direction)
51
+ const dir = ALIGN_CONTENT_DIRECTION[direction]
52
+ const x = ALIGN_CONTENT_MAP_X[alignX]
53
+ const y = ALIGN_CONTENT_MAP_Y[alignY]
54
+
55
+ return `flex-direction: ${dir}; align-items: ${isReverted ? y : x}; justify-content: ${isReverted ? x : y};`
56
+ }
57
+
58
+ export default alignContent
@@ -0,0 +1,26 @@
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
@@ -0,0 +1,16 @@
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 { Styles, StylesTheme } from "./styles"
16
+ export { default as styles } from "./styles"
@@ -0,0 +1,89 @@
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
@@ -0,0 +1,108 @@
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
@@ -0,0 +1,4 @@
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"
@@ -0,0 +1,3 @@
1
+ const camelToKebab = (s: string): string => s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)
2
+
3
+ export default camelToKebab
@@ -0,0 +1,33 @@
1
+ import { values } from "../../units"
2
+ import { borderRadius, edge } from "../shorthands"
3
+ import processDescriptor from "./processDescriptor"
4
+ import propertyMap from "./propertyMap"
5
+ import type { InnerTheme, Theme } from "./types"
6
+
7
+ export type { Theme as StylesTheme }
8
+
9
+ type Css = (strings: TemplateStringsArray, ...args: any[]) => string
10
+
11
+ export type Styles = ({
12
+ theme,
13
+ css,
14
+ rootSize,
15
+ }: {
16
+ theme: InnerTheme
17
+ css: Css
18
+ rootSize?: number
19
+ }) => string
20
+
21
+ const styles: Styles = ({ theme: t, css, rootSize }) => {
22
+ const calc = (...params: any[]) => values(params, rootSize)
23
+ const shorthand = edge(rootSize)
24
+ const borderRadiusFn = borderRadius(rootSize)
25
+
26
+ const fragments = propertyMap.map((d) =>
27
+ processDescriptor(d, t, css, calc, shorthand, borderRadiusFn),
28
+ )
29
+
30
+ return fragments.filter(Boolean).join(" ")
31
+ }
32
+
33
+ export default styles
@@ -0,0 +1,100 @@
1
+ import type { Values } from "../../units/values"
2
+ import type { BorderRadius } from "../shorthands/borderRadius"
3
+ import type { Edge } from "../shorthands/edge"
4
+ import type { PropertyDescriptor } from "./propertyMap"
5
+ import type { InnerTheme } from "./types"
6
+
7
+ type Css = (strings: TemplateStringsArray, ...values: any[]) => string
8
+ type Calc = (...params: any[]) => ReturnType<Values>
9
+
10
+ /** Mirrors the Value / PropertyValue types used by edge and borderRadius shorthands. */
11
+ type Value = string | number | null | undefined
12
+
13
+ const toCssDecl = (css: string, v: unknown) => (v == null ? "" : `${css}: ${v};`)
14
+
15
+ const processSpecial = (
16
+ d: Extract<PropertyDescriptor, { kind: "special" }>,
17
+ t: InnerTheme,
18
+ ): string => {
19
+ switch (d.id) {
20
+ case "fullScreen":
21
+ if (!t.fullScreen) return ""
22
+ return "position: fixed; top: 0; left: 0; right: 0; bottom: 0;"
23
+
24
+ case "backgroundImage":
25
+ if (!t.backgroundImage) return ""
26
+ return `background-image: url(${t.backgroundImage});`
27
+
28
+ case "animation": {
29
+ const parts = [t.keyframe, t.animation].filter(Boolean).join(" ")
30
+ return parts ? `animation: ${parts};` : ""
31
+ }
32
+
33
+ case "hideEmpty":
34
+ if (!t.hideEmpty) return ""
35
+ return "&:empty { display: none; }"
36
+
37
+ case "clearFix":
38
+ if (!t.clearFix) return ""
39
+ return '&::after { clear: both; content: ""; display: table; }'
40
+
41
+ case "extendCss":
42
+ return (t.extendCss as string) ?? ""
43
+
44
+ default:
45
+ return ""
46
+ }
47
+ }
48
+
49
+ const processDescriptor = (
50
+ d: PropertyDescriptor,
51
+ t: InnerTheme,
52
+ _css: Css,
53
+ calc: Calc,
54
+ shorthand: ReturnType<Edge>,
55
+ borderRadiusFn: ReturnType<BorderRadius>,
56
+ ): string => {
57
+ switch (d.kind) {
58
+ case "simple":
59
+ return toCssDecl(d.css, t[d.key])
60
+
61
+ case "convert":
62
+ return toCssDecl(d.css, calc(t[d.key]))
63
+
64
+ case "convert_fallback":
65
+ return toCssDecl(d.css, calc(...d.keys.map((k) => t[k])))
66
+
67
+ case "edge":
68
+ return (
69
+ shorthand(d.property, {
70
+ full: t[d.keys.full] as Value,
71
+ x: t[d.keys.x] as Value,
72
+ y: t[d.keys.y] as Value,
73
+ top: t[d.keys.top] as Value,
74
+ left: t[d.keys.left] as Value,
75
+ bottom: t[d.keys.bottom] as Value,
76
+ right: t[d.keys.right] as Value,
77
+ }) ?? ""
78
+ )
79
+
80
+ case "border_radius":
81
+ return (
82
+ borderRadiusFn({
83
+ full: t[d.keys.full] as Value,
84
+ top: t[d.keys.top] as Value,
85
+ bottom: t[d.keys.bottom] as Value,
86
+ left: t[d.keys.left] as Value,
87
+ right: t[d.keys.right] as Value,
88
+ topLeft: t[d.keys.topLeft] as Value,
89
+ topRight: t[d.keys.topRight] as Value,
90
+ bottomLeft: t[d.keys.bottomLeft] as Value,
91
+ bottomRight: t[d.keys.bottomRight] as Value,
92
+ }) ?? ""
93
+ )
94
+
95
+ case "special":
96
+ return processSpecial(d, t)
97
+ }
98
+ }
99
+
100
+ export default processDescriptor