typewritingclass 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +107 -0
  2. package/package.json +71 -0
  3. package/src/css.ts +140 -0
  4. package/src/cx.ts +105 -0
  5. package/src/dcx.ts +79 -0
  6. package/src/dynamic.ts +117 -0
  7. package/src/hash.ts +54 -0
  8. package/src/index.ts +137 -0
  9. package/src/inject.ts +86 -0
  10. package/src/layer.ts +81 -0
  11. package/src/modifiers/aria.ts +15 -0
  12. package/src/modifiers/colorScheme.ts +32 -0
  13. package/src/modifiers/data.ts +6 -0
  14. package/src/modifiers/direction.ts +5 -0
  15. package/src/modifiers/group.ts +21 -0
  16. package/src/modifiers/index.ts +17 -0
  17. package/src/modifiers/media.ts +11 -0
  18. package/src/modifiers/peer.ts +24 -0
  19. package/src/modifiers/pseudo.ts +183 -0
  20. package/src/modifiers/pseudoElements.ts +26 -0
  21. package/src/modifiers/responsive.ts +110 -0
  22. package/src/modifiers/supports.ts +6 -0
  23. package/src/registry.ts +171 -0
  24. package/src/rule.ts +202 -0
  25. package/src/runtime.ts +36 -0
  26. package/src/theme/animations.ts +11 -0
  27. package/src/theme/borders.ts +9 -0
  28. package/src/theme/colors.ts +326 -0
  29. package/src/theme/createTheme.ts +238 -0
  30. package/src/theme/filters.ts +20 -0
  31. package/src/theme/index.ts +9 -0
  32. package/src/theme/inject-theme.ts +81 -0
  33. package/src/theme/shadows.ts +8 -0
  34. package/src/theme/sizes.ts +37 -0
  35. package/src/theme/spacing.ts +44 -0
  36. package/src/theme/typography.ts +72 -0
  37. package/src/types.ts +273 -0
  38. package/src/utilities/accessibility.ts +33 -0
  39. package/src/utilities/backgrounds.ts +86 -0
  40. package/src/utilities/borders.ts +610 -0
  41. package/src/utilities/colors.ts +127 -0
  42. package/src/utilities/effects.ts +169 -0
  43. package/src/utilities/filters.ts +96 -0
  44. package/src/utilities/index.ts +57 -0
  45. package/src/utilities/interactivity.ts +253 -0
  46. package/src/utilities/layout.ts +1149 -0
  47. package/src/utilities/spacing.ts +681 -0
  48. package/src/utilities/svg.ts +34 -0
  49. package/src/utilities/tables.ts +54 -0
  50. package/src/utilities/transforms.ts +85 -0
  51. package/src/utilities/transitions.ts +98 -0
  52. package/src/utilities/typography.ts +380 -0
  53. package/src/when.ts +63 -0
package/src/index.ts ADDED
@@ -0,0 +1,137 @@
1
+ // Core API
2
+ export { cx } from './cx.ts'
3
+ export { dcx } from './dcx.ts'
4
+ export { when } from './when.ts'
5
+ export { css } from './css.ts'
6
+ export { dynamic, isDynamic } from './dynamic.ts'
7
+ export { layer } from './layer.ts'
8
+ export { generateCSS } from './registry.ts'
9
+
10
+ // Types
11
+ export type { StyleRule, Utility, Modifier, DynamicResult } from './types.ts'
12
+ export type {
13
+ Brand, CSSColor, CSSLength, CSSShadow, CSSFontWeight,
14
+ ColorInput, SpacingInput, SizeInput, RadiusInput, ShadowInput,
15
+ } from './types.ts'
16
+ export type { DynamicValue } from './dynamic.ts'
17
+
18
+ // Theme creation & switching
19
+ export { createTheme } from './theme/createTheme.ts'
20
+ export type { ThemeConfig, ThemeResult, ThemeVars } from './theme/createTheme.ts'
21
+ export { injectTheme, setTheme } from './theme/inject-theme.ts'
22
+
23
+ // Utilities
24
+ export {
25
+ // Spacing
26
+ p, px, py, pt, pr, pb, pl,
27
+ m, mx, my, mt, mr, mb, ml,
28
+ gap, gapX, gapY,
29
+ ps, pe, ms, me,
30
+ spaceX, spaceY, spaceXReverse, spaceYReverse,
31
+ // Colors
32
+ bg, textColor, borderColor,
33
+ // Typography
34
+ text, font, tracking, leading, textAlign,
35
+ fontFamily, antialiased, subpixelAntialiased,
36
+ italic, notItalic, truncate,
37
+ normalNums, ordinal, slashedZero, liningNums, oldstyleNums,
38
+ proportionalNums, tabularNums, diagonalFractions, stackedFractions,
39
+ lineClamp, listStyleImage, listStylePosition, listStyleType,
40
+ textDecoration, textDecorationColor, textDecorationStyle, textDecorationThickness,
41
+ textUnderlineOffset, textTransform, textOverflow, textWrap, textIndent,
42
+ verticalAlign, whitespace, wordBreak, hyphens, content_,
43
+ // Layout
44
+ flex, flexCol, flexRow, flexWrap, inlineFlex,
45
+ grid, gridCols, gridRows,
46
+ w, h, size, minW, minH, maxW, maxH,
47
+ display, items, justify, self,
48
+ overflow, overflowX, overflowY,
49
+ relative, absolute, fixed, sticky,
50
+ top, right, bottom, left, inset,
51
+ z,
52
+ aspectRatio, columns, breakAfter, breakBefore, breakInside,
53
+ boxDecorationBreak, boxSizing, float_, clear_, isolate, isolationAuto,
54
+ objectFit, objectPosition, overscrollBehavior, overscrollX, overscrollY,
55
+ static_, insetX, insetY, start, end,
56
+ visible, invisible, collapse_,
57
+ flexBasis, flexRowReverse, flexColReverse, flexWrapReverse, flexNowrap,
58
+ flex1, flexAuto, flexInitial, flexNone,
59
+ grow, shrink, order,
60
+ colSpan, colStart, colEnd, rowSpan, rowStart, rowEnd,
61
+ gridFlow, autoCols, autoRows,
62
+ justifyItems, justifySelf, alignContent, placeContent, placeItems, placeSelf,
63
+ container,
64
+ // Borders
65
+ rounded, roundedT, roundedB, roundedL, roundedR,
66
+ roundedTL, roundedTR, roundedBR, roundedBL,
67
+ roundedSS, roundedSE, roundedEE, roundedES,
68
+ border, borderT, borderR, borderB, borderL,
69
+ borderX, borderY, borderS, borderE, borderStyle,
70
+ ring, ringColor, ringOffsetWidth, ringOffsetColor, ringInset,
71
+ outlineWidth, outlineColor, outlineStyle, outlineOffset, outline, outlineNone,
72
+ divideX, divideY, divideColor, divideStyle, divideXReverse, divideYReverse,
73
+ // Effects
74
+ shadow, opacity, backdrop,
75
+ shadowColor, mixBlendMode, bgBlendMode,
76
+ // Interactivity
77
+ cursor, select, pointerEvents,
78
+ accentColor, appearance, caretColor, resize,
79
+ scrollBehavior, scrollMargin, scrollMarginX, scrollMarginY,
80
+ scrollMarginT, scrollMarginR, scrollMarginB, scrollMarginL,
81
+ scrollPadding, scrollPaddingX, scrollPaddingY,
82
+ scrollPaddingT, scrollPaddingR, scrollPaddingB, scrollPaddingL,
83
+ snapAlign, snapStop, snapType, touchAction, willChange,
84
+ // Filters
85
+ blur, brightness, contrast, dropShadow, grayscale, hueRotate, invert, saturate, sepia,
86
+ backdropBlur, backdropBrightness, backdropContrast, backdropGrayscale, backdropHueRotate,
87
+ backdropInvert, backdropOpacity, backdropSaturate, backdropSepia,
88
+ // Transforms
89
+ scale, scaleX, scaleY, rotate, translateX, translateY, skewX, skewY, transformOrigin, transformGpu, transformNone,
90
+ // Transitions
91
+ transition, transitionAll, transitionColors, transitionOpacity, transitionShadow, transitionTransform, transitionNone,
92
+ duration, ease, delay, animate,
93
+ // Tables
94
+ borderCollapse, borderSeparate, borderSpacing, borderSpacingX, borderSpacingY, tableLayout, captionSide,
95
+ // SVG
96
+ fill, stroke, strokeWidth,
97
+ // Accessibility
98
+ srOnly, notSrOnly, forcedColorAdjust,
99
+ // Backgrounds
100
+ bgAttachment, bgClip, bgOrigin, bgPosition, bgRepeat, bgSize, bgImage, bgGradient,
101
+ gradientFrom, gradientVia, gradientTo,
102
+ } from './utilities/index.ts'
103
+
104
+ // Modifiers
105
+ export {
106
+ // Pseudo-classes
107
+ hover, focus, active, disabled, focusVisible, focusWithin, firstChild, lastChild,
108
+ visited, checked, indeterminate, default_, required_, valid, invalid,
109
+ inRange, outOfRange, placeholderShown, autofill, readOnly, empty,
110
+ even, odd, firstOfType, lastOfType, onlyChild, onlyOfType, target, open_,
111
+ has_,
112
+ // Responsive
113
+ sm, md, lg, xl, _2xl,
114
+ maxSm, maxMd, maxLg, maxXl, max2xl,
115
+ // Color scheme
116
+ dark,
117
+ // Media
118
+ motionReduce, motionSafe, print_, portrait, landscape, contrastMore, contrastLess, forcedColors,
119
+ // Pseudo-elements
120
+ before, after, placeholder_, file_, marker, selection_, firstLine, firstLetter, backdrop_,
121
+ // ARIA
122
+ ariaChecked, ariaDisabled, ariaExpanded, ariaHidden, ariaPressed, ariaReadonly, ariaRequired, ariaSelected, aria,
123
+ // Data
124
+ data,
125
+ // Supports
126
+ supports,
127
+ // Group
128
+ groupHover, groupFocus, groupActive, groupFocusVisible, groupFocusWithin,
129
+ groupDisabled, groupChecked, groupEmpty, groupFirst, groupLast, groupOdd, groupEven,
130
+ groupOpen, groupVisited, groupHas,
131
+ // Peer
132
+ peerHover, peerFocus, peerActive, peerFocusVisible, peerDisabled,
133
+ peerChecked, peerInvalid, peerRequired, peerPlaceholderShown,
134
+ peerFocusWithin, peerEmpty, peerFirst, peerLast, peerOdd, peerEven, peerOpen, peerVisited, peerHas,
135
+ // Direction
136
+ rtl, ltr,
137
+ } from './modifiers/index.ts'
package/src/inject.ts ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Runtime style injection module.
3
+ *
4
+ * This module automatically creates a `<style id="twc">` element in the
5
+ * document head and keeps it synchronised with the {@link registry}. Whenever
6
+ * a new rule is registered, a microtask is scheduled to regenerate the full
7
+ * CSS and update the style element.
8
+ *
9
+ * The module self-initialises on import. In non-browser environments (e.g., SSR)
10
+ * the initialisation is silently skipped.
11
+ *
12
+ * @internal
13
+ * @module
14
+ */
15
+
16
+ import { generateCSS, onChange } from './registry.ts'
17
+
18
+ /**
19
+ * Cached reference to the `<style id="twc">` element managed by this module.
20
+ *
21
+ * @internal
22
+ */
23
+ let styleEl: HTMLStyleElement | null = null
24
+
25
+ /**
26
+ * Whether a CSS update microtask is already scheduled.
27
+ *
28
+ * @internal
29
+ */
30
+ let pending = false
31
+
32
+ /**
33
+ * Writes the latest generated CSS into the `<style>` element.
34
+ *
35
+ * Called as a microtask callback. Resets the `pending` flag so that
36
+ * subsequent registry changes will schedule a new update.
37
+ *
38
+ * @internal
39
+ */
40
+ function flush(): void {
41
+ pending = false
42
+ if (!styleEl) return
43
+ styleEl.textContent = generateCSS()
44
+ }
45
+
46
+ /**
47
+ * Schedules a CSS update on the next microtask if one is not already pending.
48
+ *
49
+ * Multiple synchronous rule registrations are batched into a single DOM write,
50
+ * avoiding redundant style recalculations.
51
+ *
52
+ * @internal
53
+ */
54
+ function scheduleUpdate(): void {
55
+ if (pending) return
56
+ pending = true
57
+ queueMicrotask(flush)
58
+ }
59
+
60
+ /**
61
+ * Initialises the runtime style injection.
62
+ *
63
+ * - Looks for an existing `<style id="twc">` element or creates one.
64
+ * - Subscribes to registry changes via {@link onChange}.
65
+ * - Triggers an initial CSS flush.
66
+ *
67
+ * This function is a no-op in non-browser environments (SSR-safe).
68
+ *
69
+ * @internal
70
+ */
71
+ function init(): void {
72
+ if (typeof document === 'undefined') return
73
+ if (styleEl) return
74
+
75
+ styleEl = document.getElementById('twc') as HTMLStyleElement | null
76
+ if (!styleEl) {
77
+ styleEl = document.createElement('style')
78
+ styleEl.id = 'twc'
79
+ document.head.appendChild(styleEl)
80
+ }
81
+
82
+ onChange(scheduleUpdate)
83
+ scheduleUpdate()
84
+ }
85
+
86
+ init()
package/src/layer.ts ADDED
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Monotonically increasing counter used to assign a unique ordering layer
3
+ * to each style rule. This controls the order rules appear in the generated
4
+ * CSS, ensuring that later-declared utilities override earlier ones.
5
+ *
6
+ * @internal
7
+ */
8
+ let globalLayer = 0
9
+
10
+ /**
11
+ * Returns the next layer number and increments the global counter.
12
+ *
13
+ * Each call returns a value one greater than the previous call, starting
14
+ * from `0`. The layer number is embedded into each registered rule to
15
+ * control CSS source order: rules with lower layer numbers appear first.
16
+ *
17
+ * @internal
18
+ * @returns The next sequential layer number.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * nextLayer() // 0
23
+ * nextLayer() // 1
24
+ * nextLayer() // 2
25
+ * ```
26
+ */
27
+ export function nextLayer(): number {
28
+ return globalLayer++
29
+ }
30
+
31
+ /**
32
+ * Assigns an explicit layer priority to a style rule.
33
+ *
34
+ * By default, rules receive auto-incremented layer numbers based on
35
+ * declaration order. Use `layer()` to override this and force a rule
36
+ * to a specific priority level. Higher numbers = higher priority
37
+ * (overrides lower).
38
+ *
39
+ * @param priority - The explicit layer number to assign.
40
+ * @returns A function that accepts style rules and wraps them with the given layer.
41
+ *
42
+ * @example Force a reset to lowest priority
43
+ * ```ts
44
+ * import { cx, layer, p, bg } from 'typewritingclass'
45
+ *
46
+ * cx(layer(0)(bg('white')), p(4))
47
+ * // bg('white') is forced to layer 0, p(4) gets auto-assigned layer 1
48
+ * // p(4) overrides any conflicting property from bg('white')
49
+ * ```
50
+ *
51
+ * @example Force an override to highest priority
52
+ * ```ts
53
+ * cx(p(4), layer(1000)(p(8)))
54
+ * // p(8) at layer 1000 always overrides p(4)
55
+ * ```
56
+ */
57
+ export function layer(priority: number): (...rules: import('./types.ts').StyleRule[]) => import('./types.ts').StyleRule {
58
+ return (...rules) => {
59
+ const combined = rules.length === 1
60
+ ? rules[0]
61
+ : {
62
+ _tag: 'StyleRule' as const,
63
+ declarations: Object.assign({}, ...rules.map(r => r.declarations)),
64
+ selectors: [...new Set(rules.flatMap(r => r.selectors))],
65
+ mediaQueries: [...new Set(rules.flatMap(r => r.mediaQueries))],
66
+ ...(rules.some(r => r.dynamicBindings) ? {
67
+ dynamicBindings: Object.assign({}, ...rules.filter(r => r.dynamicBindings).map(r => r.dynamicBindings)),
68
+ } : {}),
69
+ }
70
+ return { ...combined, _layer: priority } as any
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Resets the global layer counter back to `0`.
76
+ *
77
+ * @internal Exposed for testing only. Do not call in production code.
78
+ */
79
+ export function _resetLayer(): void {
80
+ globalLayer = 0
81
+ }
@@ -0,0 +1,15 @@
1
+ import type { StyleRule, Modifier } from '../types.ts'
2
+ import { wrapWithSelector } from '../rule.ts'
3
+
4
+ export const ariaChecked: Modifier = (rule: StyleRule) => wrapWithSelector(rule, '[aria-checked="true"]')
5
+ export const ariaDisabled: Modifier = (rule: StyleRule) => wrapWithSelector(rule, '[aria-disabled="true"]')
6
+ export const ariaExpanded: Modifier = (rule: StyleRule) => wrapWithSelector(rule, '[aria-expanded="true"]')
7
+ export const ariaHidden: Modifier = (rule: StyleRule) => wrapWithSelector(rule, '[aria-hidden="true"]')
8
+ export const ariaPressed: Modifier = (rule: StyleRule) => wrapWithSelector(rule, '[aria-pressed="true"]')
9
+ export const ariaReadonly: Modifier = (rule: StyleRule) => wrapWithSelector(rule, '[aria-readonly="true"]')
10
+ export const ariaRequired: Modifier = (rule: StyleRule) => wrapWithSelector(rule, '[aria-required="true"]')
11
+ export const ariaSelected: Modifier = (rule: StyleRule) => wrapWithSelector(rule, '[aria-selected="true"]')
12
+
13
+ export function aria(attr: string): Modifier {
14
+ return (rule: StyleRule) => wrapWithSelector(rule, `[aria-${attr}]`)
15
+ }
@@ -0,0 +1,32 @@
1
+ import type { StyleRule, Modifier } from '../types.ts'
2
+ import { wrapWithMediaQuery } from '../rule.ts'
3
+
4
+ /**
5
+ * Applies styles only when the user's operating system or browser is set to a dark color scheme.
6
+ *
7
+ * Use with {@link when} to conditionally apply style rules inside a
8
+ * `@media (prefers-color-scheme: dark)` query. This responds to the user's
9
+ * system-level preference, not a manual theme toggle. For manual theme switching,
10
+ * see {@link setTheme} and the theme API.
11
+ *
12
+ * @param rule - The style rule to apply in dark mode.
13
+ * @returns A new {@link StyleRule} wrapped in the `prefers-color-scheme: dark` media query.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { cx, bg, color, when, dark } from 'typewritingclass'
18
+ *
19
+ * cx(
20
+ * bg('#ffffff'),
21
+ * color('#111827'),
22
+ * when(dark)(bg('#111827')),
23
+ * when(dark)(color('#f9fafb')),
24
+ * )
25
+ * // CSS: .abc { background-color: #ffffff; }
26
+ * // .def { color: #111827; }
27
+ * // @media (prefers-color-scheme: dark) { .ghi { background-color: #111827; } }
28
+ * // @media (prefers-color-scheme: dark) { .jkl { color: #f9fafb; } }
29
+ * ```
30
+ */
31
+ export const dark: Modifier = (rule: StyleRule) =>
32
+ wrapWithMediaQuery(rule, '(prefers-color-scheme: dark)')
@@ -0,0 +1,6 @@
1
+ import type { StyleRule, Modifier } from '../types.ts'
2
+ import { wrapWithSelector } from '../rule.ts'
3
+
4
+ export function data(attr: string): Modifier {
5
+ return (rule: StyleRule) => wrapWithSelector(rule, `[data-${attr}]`)
6
+ }
@@ -0,0 +1,5 @@
1
+ import type { StyleRule, Modifier } from '../types.ts'
2
+ import { wrapWithSelectorTemplate } from '../rule.ts'
3
+
4
+ export const rtl: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '[dir="rtl"] &')
5
+ export const ltr: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '[dir="ltr"] &')
@@ -0,0 +1,21 @@
1
+ import type { StyleRule, Modifier } from '../types.ts'
2
+ import { wrapWithSelectorTemplate } from '../rule.ts'
3
+
4
+ export const groupHover: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.group:hover &')
5
+ export const groupFocus: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.group:focus &')
6
+ export const groupActive: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.group:active &')
7
+ export const groupFocusVisible: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.group:focus-visible &')
8
+ export const groupFocusWithin: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.group:focus-within &')
9
+ export const groupDisabled: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.group:disabled &')
10
+ export const groupChecked: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.group:checked &')
11
+ export const groupEmpty: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.group:empty &')
12
+ export const groupFirst: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.group:first-child &')
13
+ export const groupLast: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.group:last-child &')
14
+ export const groupOdd: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.group:nth-child(odd) &')
15
+ export const groupEven: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.group:nth-child(even) &')
16
+ export const groupOpen: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.group[open] &')
17
+ export const groupVisited: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.group:visited &')
18
+
19
+ export function groupHas(selector: string): Modifier {
20
+ return (rule: StyleRule) => wrapWithSelectorTemplate(rule, `.group:has(${selector}) &`)
21
+ }
@@ -0,0 +1,17 @@
1
+ export {
2
+ hover, focus, active, disabled, focusVisible, focusWithin, firstChild, lastChild,
3
+ visited, checked, indeterminate, default_, required_, valid, invalid,
4
+ inRange, outOfRange, placeholderShown, autofill, readOnly, empty,
5
+ even, odd, firstOfType, lastOfType, onlyChild, onlyOfType, target, open_,
6
+ has_,
7
+ } from './pseudo.ts'
8
+ export { sm, md, lg, xl, _2xl, maxSm, maxMd, maxLg, maxXl, max2xl } from './responsive.ts'
9
+ export { dark } from './colorScheme.ts'
10
+ export { motionReduce, motionSafe, print_, portrait, landscape, contrastMore, contrastLess, forcedColors } from './media.ts'
11
+ export { before, after, placeholder_, file_, marker, selection_, firstLine, firstLetter, backdrop_ } from './pseudoElements.ts'
12
+ export { ariaChecked, ariaDisabled, ariaExpanded, ariaHidden, ariaPressed, ariaReadonly, ariaRequired, ariaSelected, aria } from './aria.ts'
13
+ export { data } from './data.ts'
14
+ export { supports } from './supports.ts'
15
+ export { groupHover, groupFocus, groupActive, groupFocusVisible, groupFocusWithin, groupDisabled, groupChecked, groupEmpty, groupFirst, groupLast, groupOdd, groupEven, groupOpen, groupVisited, groupHas } from './group.ts'
16
+ export { peerHover, peerFocus, peerActive, peerFocusVisible, peerDisabled, peerChecked, peerInvalid, peerRequired, peerPlaceholderShown, peerFocusWithin, peerEmpty, peerFirst, peerLast, peerOdd, peerEven, peerOpen, peerVisited, peerHas } from './peer.ts'
17
+ export { rtl, ltr } from './direction.ts'
@@ -0,0 +1,11 @@
1
+ import type { StyleRule, Modifier } from '../types.ts'
2
+ import { wrapWithMediaQuery } from '../rule.ts'
3
+
4
+ export const motionReduce: Modifier = (rule: StyleRule) => wrapWithMediaQuery(rule, '(prefers-reduced-motion: reduce)')
5
+ export const motionSafe: Modifier = (rule: StyleRule) => wrapWithMediaQuery(rule, '(prefers-reduced-motion: no-preference)')
6
+ export const print_: Modifier = (rule: StyleRule) => wrapWithMediaQuery(rule, 'print')
7
+ export const portrait: Modifier = (rule: StyleRule) => wrapWithMediaQuery(rule, '(orientation: portrait)')
8
+ export const landscape: Modifier = (rule: StyleRule) => wrapWithMediaQuery(rule, '(orientation: landscape)')
9
+ export const contrastMore: Modifier = (rule: StyleRule) => wrapWithMediaQuery(rule, '(prefers-contrast: more)')
10
+ export const contrastLess: Modifier = (rule: StyleRule) => wrapWithMediaQuery(rule, '(prefers-contrast: less)')
11
+ export const forcedColors: Modifier = (rule: StyleRule) => wrapWithMediaQuery(rule, '(forced-colors: active)')
@@ -0,0 +1,24 @@
1
+ import type { StyleRule, Modifier } from '../types.ts'
2
+ import { wrapWithSelectorTemplate } from '../rule.ts'
3
+
4
+ export const peerHover: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.peer:hover ~ &')
5
+ export const peerFocus: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.peer:focus ~ &')
6
+ export const peerActive: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.peer:active ~ &')
7
+ export const peerFocusVisible: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.peer:focus-visible ~ &')
8
+ export const peerDisabled: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.peer:disabled ~ &')
9
+ export const peerChecked: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.peer:checked ~ &')
10
+ export const peerInvalid: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.peer:invalid ~ &')
11
+ export const peerRequired: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.peer:required ~ &')
12
+ export const peerPlaceholderShown: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.peer:placeholder-shown ~ &')
13
+ export const peerFocusWithin: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.peer:focus-within ~ &')
14
+ export const peerEmpty: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.peer:empty ~ &')
15
+ export const peerFirst: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.peer:first-child ~ &')
16
+ export const peerLast: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.peer:last-child ~ &')
17
+ export const peerOdd: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.peer:nth-child(odd) ~ &')
18
+ export const peerEven: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.peer:nth-child(even) ~ &')
19
+ export const peerOpen: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.peer[open] ~ &')
20
+ export const peerVisited: Modifier = (rule: StyleRule) => wrapWithSelectorTemplate(rule, '.peer:visited ~ &')
21
+
22
+ export function peerHas(selector: string): Modifier {
23
+ return (rule: StyleRule) => wrapWithSelectorTemplate(rule, `.peer:has(${selector}) ~ &`)
24
+ }
@@ -0,0 +1,183 @@
1
+ import type { StyleRule, Modifier } from '../types.ts'
2
+ import { wrapWithSelector } from '../rule.ts'
3
+
4
+ /**
5
+ * Applies styles only when the element is hovered.
6
+ *
7
+ * Use with {@link when} to conditionally apply style rules on `:hover`.
8
+ *
9
+ * @param rule - The style rule to apply on hover.
10
+ * @returns A new {@link StyleRule} scoped to the `:hover` pseudo-class.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { cx, bg, when, hover } from 'typewritingclass'
15
+ *
16
+ * cx(bg('#3b82f6'), when(hover)(bg('#2563eb')))
17
+ * // CSS: .abc { background-color: #3b82f6; }
18
+ * // .def:hover { background-color: #2563eb; }
19
+ * ```
20
+ */
21
+ export const hover: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':hover')
22
+
23
+ /**
24
+ * Applies styles only when the element has keyboard or pointer focus.
25
+ *
26
+ * Use with {@link when} to conditionally apply style rules on `:focus`.
27
+ *
28
+ * @param rule - The style rule to apply on focus.
29
+ * @returns A new {@link StyleRule} scoped to the `:focus` pseudo-class.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * import { cx, borderColor, when, focus } from 'typewritingclass'
34
+ *
35
+ * cx(borderColor('#d1d5db'), when(focus)(borderColor('#3b82f6')))
36
+ * // CSS: .abc { border-color: #d1d5db; }
37
+ * // .def:focus { border-color: #3b82f6; }
38
+ * ```
39
+ */
40
+ export const focus: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':focus')
41
+
42
+ /**
43
+ * Applies styles only when the element is being actively pressed.
44
+ *
45
+ * Use with {@link when} to conditionally apply style rules on `:active`.
46
+ *
47
+ * @param rule - The style rule to apply while the element is active.
48
+ * @returns A new {@link StyleRule} scoped to the `:active` pseudo-class.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { cx, bg, when, active } from 'typewritingclass'
53
+ *
54
+ * cx(bg('#3b82f6'), when(active)(bg('#1d4ed8')))
55
+ * // CSS: .abc { background-color: #3b82f6; }
56
+ * // .def:active { background-color: #1d4ed8; }
57
+ * ```
58
+ */
59
+ export const active: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':active')
60
+
61
+ /**
62
+ * Applies styles only when the element is disabled.
63
+ *
64
+ * Use with {@link when} to conditionally apply style rules on `:disabled`.
65
+ * Commonly used for form controls such as buttons and inputs.
66
+ *
67
+ * @param rule - The style rule to apply when disabled.
68
+ * @returns A new {@link StyleRule} scoped to the `:disabled` pseudo-class.
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * import { cx, opacity, when, disabled } from 'typewritingclass'
73
+ *
74
+ * cx(opacity('1'), when(disabled)(opacity('0.5')))
75
+ * // CSS: .abc { opacity: 1; }
76
+ * // .def:disabled { opacity: 0.5; }
77
+ * ```
78
+ */
79
+ export const disabled: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':disabled')
80
+
81
+ /**
82
+ * Applies styles only when the element has visible focus (typically keyboard focus).
83
+ *
84
+ * Use with {@link when} to conditionally apply style rules on `:focus-visible`.
85
+ * Unlike {@link focus}, this only matches when the user agent determines that
86
+ * focus should be visibly indicated (e.g., keyboard navigation, not mouse clicks).
87
+ *
88
+ * @param rule - The style rule to apply on visible focus.
89
+ * @returns A new {@link StyleRule} scoped to the `:focus-visible` pseudo-class.
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * import { cx, outline, when, focusVisible } from 'typewritingclass'
94
+ *
95
+ * cx(when(focusVisible)(outline('2px solid #3b82f6')))
96
+ * // CSS: .abc:focus-visible { outline: 2px solid #3b82f6; }
97
+ * ```
98
+ */
99
+ export const focusVisible: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':focus-visible')
100
+
101
+ /**
102
+ * Applies styles when the element or any of its descendants has focus.
103
+ *
104
+ * Use with {@link when} to conditionally apply style rules on `:focus-within`.
105
+ * Useful for styling a parent container when a child input receives focus.
106
+ *
107
+ * @param rule - The style rule to apply when focus is within the element.
108
+ * @returns A new {@link StyleRule} scoped to the `:focus-within` pseudo-class.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * import { cx, borderColor, when, focusWithin } from 'typewritingclass'
113
+ *
114
+ * cx(borderColor('#d1d5db'), when(focusWithin)(borderColor('#3b82f6')))
115
+ * // CSS: .abc { border-color: #d1d5db; }
116
+ * // .def:focus-within { border-color: #3b82f6; }
117
+ * ```
118
+ */
119
+ export const focusWithin: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':focus-within')
120
+
121
+ /**
122
+ * Applies styles only when the element is the first child of its parent.
123
+ *
124
+ * Use with {@link when} to conditionally apply style rules on `:first-child`.
125
+ *
126
+ * @param rule - The style rule to apply to the first child.
127
+ * @returns A new {@link StyleRule} scoped to the `:first-child` pseudo-class.
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * import { cx, mt, when, firstChild } from 'typewritingclass'
132
+ *
133
+ * cx(mt('1rem'), when(firstChild)(mt('0')))
134
+ * // CSS: .abc { margin-top: 1rem; }
135
+ * // .def:first-child { margin-top: 0; }
136
+ * ```
137
+ */
138
+ export const firstChild: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':first-child')
139
+
140
+ /**
141
+ * Applies styles only when the element is the last child of its parent.
142
+ *
143
+ * Use with {@link when} to conditionally apply style rules on `:last-child`.
144
+ *
145
+ * @param rule - The style rule to apply to the last child.
146
+ * @returns A new {@link StyleRule} scoped to the `:last-child` pseudo-class.
147
+ *
148
+ * @example
149
+ * ```ts
150
+ * import { cx, mb, when, lastChild } from 'typewritingclass'
151
+ *
152
+ * cx(mb('1rem'), when(lastChild)(mb('0')))
153
+ * // CSS: .abc { margin-bottom: 1rem; }
154
+ * // .def:last-child { margin-bottom: 0; }
155
+ * ```
156
+ */
157
+ export const lastChild: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':last-child')
158
+
159
+ export const visited: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':visited')
160
+ export const checked: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':checked')
161
+ export const indeterminate: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':indeterminate')
162
+ export const default_: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':default')
163
+ export const required_: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':required')
164
+ export const valid: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':valid')
165
+ export const invalid: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':invalid')
166
+ export const inRange: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':in-range')
167
+ export const outOfRange: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':out-of-range')
168
+ export const placeholderShown: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':placeholder-shown')
169
+ export const autofill: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':autofill')
170
+ export const readOnly: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':read-only')
171
+ export const empty: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':empty')
172
+ export const even: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':nth-child(even)')
173
+ export const odd: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':nth-child(odd)')
174
+ export const firstOfType: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':first-of-type')
175
+ export const lastOfType: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':last-of-type')
176
+ export const onlyChild: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':only-child')
177
+ export const onlyOfType: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':only-of-type')
178
+ export const target: Modifier = (rule: StyleRule) => wrapWithSelector(rule, ':target')
179
+ export const open_: Modifier = (rule: StyleRule) => wrapWithSelector(rule, '[open]')
180
+
181
+ export function has_(selector: string): Modifier {
182
+ return (rule: StyleRule) => wrapWithSelector(rule, `:has(${selector})`)
183
+ }
@@ -0,0 +1,26 @@
1
+ import type { StyleRule, Modifier } from '../types.ts'
2
+ import { wrapWithSelector } from '../rule.ts'
3
+
4
+ export const before: Modifier = (rule: StyleRule) => {
5
+ const result = wrapWithSelector(rule, '::before')
6
+ if (!result.declarations.content) {
7
+ result.declarations = { content: '""', ...result.declarations }
8
+ }
9
+ return result
10
+ }
11
+
12
+ export const after: Modifier = (rule: StyleRule) => {
13
+ const result = wrapWithSelector(rule, '::after')
14
+ if (!result.declarations.content) {
15
+ result.declarations = { content: '""', ...result.declarations }
16
+ }
17
+ return result
18
+ }
19
+
20
+ export const placeholder_: Modifier = (rule: StyleRule) => wrapWithSelector(rule, '::placeholder')
21
+ export const file_: Modifier = (rule: StyleRule) => wrapWithSelector(rule, '::file-selector-button')
22
+ export const marker: Modifier = (rule: StyleRule) => wrapWithSelector(rule, '::marker')
23
+ export const selection_: Modifier = (rule: StyleRule) => wrapWithSelector(rule, '::selection')
24
+ export const firstLine: Modifier = (rule: StyleRule) => wrapWithSelector(rule, '::first-line')
25
+ export const firstLetter: Modifier = (rule: StyleRule) => wrapWithSelector(rule, '::first-letter')
26
+ export const backdrop_: Modifier = (rule: StyleRule) => wrapWithSelector(rule, '::backdrop')