next-style 1.1.9 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,7 @@ Designed for **page-scoped and component-scoped usage** without build-time tooli
9
9
  ## Package Information
10
10
 
11
11
  - **Name:** next-style
12
- - **Version:** 1.1.5
12
+ - **Version:** 1.2.1
13
13
  - **License:** MIT
14
14
  - **Author:** kingslimes
15
15
  https://github.com/kingslimes
@@ -115,6 +115,29 @@ export default function HomePage() {
115
115
 
116
116
  ## Styling API
117
117
 
118
+ ### `resetStyle(): this as NextStyle`
119
+
120
+ Reset default style
121
+
122
+ ``` ts
123
+ const { css, ... } = new NextStyle().resetStyle()
124
+ ```
125
+ or
126
+ ``` ts
127
+ const { resetStyle } = new NextStyle()
128
+ resetStyle()
129
+ ```
130
+
131
+ ### `root(style): void`
132
+
133
+ Create root style from a style object.
134
+
135
+ ``` ts
136
+ root({
137
+ "--color-base": "#fff"
138
+ })
139
+ ```
140
+
118
141
  ### `css(style): string`
119
142
 
120
143
  Creates a class name from a style object.
package/dist/index.d.ts CHANGED
@@ -1,10 +1,28 @@
1
1
  import type { Properties } from "csstype";
2
2
  import type { DetailedReactHTMLElement } from "react";
3
3
  /**
4
- * Style object supported by next-style
4
+ * Style object used by NextStyle.
5
+ *
6
+ * Supports:
7
+ * - Standard CSS properties (camelCase)
8
+ * - Pseudo states via `_hover`, `_focus`, `_active`
9
+ * - Responsive media queries via `_sm` ~ `_xxl`
10
+ *
11
+ * Example:
12
+ * ```ts
13
+ * const button = css({
14
+ * padding: "8px 16px",
15
+ * backgroundColor: "black",
16
+ * color: "white",
17
+ * _hover: { opacity: 0.8 },
18
+ * _md: { padding: "12px 20px" }
19
+ * })
20
+ * ```
5
21
  */
6
22
  export type NextStyleProperties = {
7
23
  [K in keyof Properties<string | number>]?: Properties<string | number>[K];
24
+ } & {
25
+ [customProperty: `--${string}`]: string | number;
8
26
  } & {
9
27
  _hover?: NextStyleObject;
10
28
  _focus?: NextStyleObject;
@@ -28,42 +46,270 @@ type FontFaceObject = {
28
46
  unicodeRange?: string;
29
47
  };
30
48
  type PseudoState = "hover" | "focus" | "active";
49
+ /**
50
+ * Builder for defining relations starting from a generated class name.
51
+ *
52
+ * This builder should only be created via `NextStyle.when(...)`.
53
+ */
31
54
  declare class RelationBuilder {
32
55
  private ns;
33
56
  private source;
34
57
  private pseudo?;
58
+ /**
59
+ * @internal
60
+ * @param ns NextStyle instance
61
+ * @param source Source class name generated by `css()`
62
+ * @param pseudo Optional pseudo state applied to the source
63
+ */
35
64
  constructor(ns: NextStyle, source: string, pseudo?: PseudoState | undefined);
65
+ /**
66
+ * Apply `:hover` pseudo state to the source selector.
67
+ *
68
+ * All subsequent relations will be scoped under `:hover`.
69
+ *
70
+ * @returns RelationBuilder
71
+ *
72
+ * Example:
73
+ * ```ts
74
+ * when(card)
75
+ * .hover()
76
+ * .child(icon, { opacity: 1 })
77
+ * ```
78
+ */
36
79
  hover(): RelationBuilder;
80
+ /**
81
+ * Apply `:focus` pseudo state to the source selector.
82
+ *
83
+ * @returns RelationBuilder
84
+ */
37
85
  focus(): RelationBuilder;
86
+ /**
87
+ * Apply `:active` pseudo state to the source selector.
88
+ *
89
+ * @returns RelationBuilder
90
+ */
38
91
  active(): RelationBuilder;
92
+ /**
93
+ * Define styles for an adjacent sibling selector.
94
+ *
95
+ * CSS equivalent:
96
+ * `.source + .target`
97
+ *
98
+ * @param target Target class name generated by `css()`
99
+ * @param style Style applied to the target element
100
+ *
101
+ * Example:
102
+ * ```ts
103
+ * when(input)
104
+ * .focus()
105
+ * .adjacent(label, { color: "red" })
106
+ * ```
107
+ */
39
108
  adjacent(target: string, style: NextStyleProperties): void;
109
+ /**
110
+ * Define styles for a direct child selector.
111
+ *
112
+ * CSS equivalent:
113
+ * `.source > .target`
114
+ *
115
+ * @param target Target class name generated by `css()`
116
+ * @param style Style applied to the target element
117
+ */
40
118
  child(target: string, style: NextStyleProperties): void;
119
+ /**
120
+ * Define styles for a general sibling selector.
121
+ *
122
+ * CSS equivalent:
123
+ * `.source ~ .target`
124
+ *
125
+ * @param target Target class name generated by `css()`
126
+ * @param style Style applied to the target element
127
+ */
41
128
  sibling(target: string, style: NextStyleProperties): void;
129
+ /**
130
+ * Define styles for a descendant selector.
131
+ *
132
+ * CSS equivalent:
133
+ * `.source .target`
134
+ *
135
+ * @param target Target class name generated by `css()`
136
+ * @param style Style applied to the target element
137
+ */
42
138
  descendant(target: string, style: NextStyleProperties): void;
139
+ /**
140
+ * @internal
141
+ * Emit a global relational rule.
142
+ */
43
143
  private emit;
44
144
  }
45
145
  /**
46
- * NextStyle runtime CSS-in-JS engine
146
+ * NextStyle
147
+ *
148
+ * Lightweight runtime CSS-in-JS engine with deterministic class names.
149
+ *
150
+ * Features:
151
+ * - Scoped class generation
152
+ * - Nested pseudo selectors
153
+ * - Responsive media queries
154
+ * - Global styles
155
+ * - Keyframes and font-face support
156
+ *
157
+ * Example:
158
+ * ```ts
159
+ * const ns = new NextStyle("app")
160
+ * ```
47
161
  */
48
162
  export declare class NextStyle {
49
163
  private prefix;
50
164
  private rules;
51
- /** store merged global style objects */
52
165
  private globalStore;
53
166
  constructor(prefix?: string);
167
+ /**
168
+ * Generate a scoped class name from a style object.
169
+ *
170
+ * The returned value is a branded class name and is intended
171
+ * to be used with `when(...)` and relational APIs.
172
+ *
173
+ * @param style Style definition object
174
+ * @returns Scoped class name
175
+ *
176
+ * Example:
177
+ * ```ts
178
+ * const button = css({ padding: 8 })
179
+ * ```
180
+ */
54
181
  css: (style: NextStyleProperties) => string;
55
182
  /**
56
- * Global style with property-level merge
183
+ * Define a global CSS selector.
184
+ *
185
+ * Styles are merged at property level instead of overwritten.
186
+ *
187
+ * @param selector CSS selector
188
+ * @param style Style definition
189
+ *
190
+ * Example:
191
+ * ```ts
192
+ * global("body", {
193
+ * fontFamily: "Kanit, sans-serif"
194
+ * })
195
+ * ```
57
196
  */
58
197
  global: (selector: string, style: NextStyleProperties) => void;
198
+ /**
199
+ * Define styles on `:root`.
200
+ *
201
+ * Intended for:
202
+ * - CSS variables
203
+ * - Global theme tokens
204
+ *
205
+ * Styles are merged at property level.
206
+ *
207
+ * Example:
208
+ * ```ts
209
+ * root({
210
+ * "--color-primary": "#4f46e5",
211
+ * "--radius": "12px"
212
+ * })
213
+ * ```
214
+ */
215
+ root: (style: NextStyleProperties) => void;
59
216
  /**
60
217
  * Apply default browser reset styles
218
+ * ```css
219
+ * html, body {
220
+ * max-width: 100vw;
221
+ * overflow-x: hidden;
222
+ * }
223
+ * body {
224
+ * color: "black";
225
+ * background: "white";
226
+ * -moz-osx-font-smoothing: grayscale;
227
+ * -webkit-font-smoothing: antialiased;
228
+ * font-family: "Arial, Helvetica, sans-serif"
229
+ * }
230
+ * *,
231
+ * *::before,
232
+ * *::after {
233
+ * margin: 0;
234
+ * padding: 0;
235
+ * box-sizing: border-box;
236
+ * }
237
+ * a {
238
+ * color: inherit;
239
+ * text-decoration: none
240
+ * }
241
+ * ```
242
+ */
243
+ resetStyle: () => this;
244
+ /**
245
+ * Create relational selectors based on a class generated by `css(...)`.
246
+ *
247
+ * ⚠️ This method only accepts class names returned from `css`.
248
+ *
249
+ * @param source Class name generated by `css`
250
+ *
251
+ * Example:
252
+ * ```ts
253
+ * const card = css({ ... })
254
+ *
255
+ * when(card)
256
+ * .hover()
257
+ * .child("icon", { opacity: 1 })
258
+ * ```
61
259
  */
62
- resetBrowserStyle: () => void;
63
260
  when: (source: string) => RelationBuilder;
261
+ /**
262
+ * Register keyframes animation.
263
+ *
264
+ * @param frames Keyframes definition
265
+ * @returns Generated animation name
266
+ *
267
+ * Example:
268
+ * ```ts
269
+ * const fadeIn = keyframes({
270
+ * from: { opacity: 0 },
271
+ * to: { opacity: 1 }
272
+ * })
273
+ * ```
274
+ */
64
275
  keyframes: (frames: KeyframesObject) => string;
276
+ /**
277
+ * Register a `@font-face` rule.
278
+ *
279
+ * Each unique definition is injected only once.
280
+ *
281
+ * @param font Font-face definition
282
+ *
283
+ * Example:
284
+ * ```ts
285
+ * fontFace({
286
+ * fontFamily: "Kanit",
287
+ * src: "url(/kanit.woff2) format('woff2')",
288
+ * fontWeight: 400
289
+ * })
290
+ * ```
291
+ */
65
292
  fontFace: (font: FontFaceObject) => void;
293
+ /**
294
+ * Serialize all generated CSS into a single string.
295
+ *
296
+ * Intended for:
297
+ * - Manual `<style>` injection
298
+ * - SSR environments
299
+ *
300
+ * @returns CSS text or `null` if no styles exist
301
+ */
66
302
  toTextCss: () => string | null;
303
+ /**
304
+ * React component that injects generated CSS into a `<style>` tag.
305
+ *
306
+ * Returns `null` if no styles are registered.
307
+ *
308
+ * Example:
309
+ * ```tsx
310
+ * <StyleProvider />
311
+ * ```
312
+ */
67
313
  StyleProvider: () => DetailedReactHTMLElement<{
68
314
  children: string;
69
315
  }, HTMLStyleElement> | null;
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import postcss from"postcss";import{createElement}from"react";import autoprefixer from"autoprefixer";var processor=postcss([autoprefixer({overrideBrowserslist:[">0.2%","not dead","not op_mini all"]})]),postcssCache=new Map;function postcssTransform(cssText){let cached=postcssCache.get(cssText);if(cached)return cached;let result=processor.process(cssText,{from:void 0}).css;return postcssCache.set(cssText,result),result}function stableStringify(value){if(value==null||typeof value!=="object")return JSON.stringify(value);if(Array.isArray(value))return`[${value.map(stableStringify).join(",")}]`;return`{${Object.keys(value).sort().map((k)=>`"${k}":${stableStringify(value[k])}`).join(",")}}`}function createHashName(seed){let hash=BigInt("0xcbf29ce484222325"),prime=BigInt("0x100000001b3");for(let i=0;i<seed.length;i++)hash^=BigInt(seed.charCodeAt(i)),hash*=prime,hash&=BigInt("0xffffffffffffffff");return hash.toString(36).slice(0,9)}function toKebabCase(prop){return prop.replace(/[A-Z]/g,(m)=>`-${m.toLowerCase()}`)}var MEDIA_MAP={_sm:"(min-width:640px)",_md:"(min-width:768px)",_lg:"(min-width:1024px)",_xl:"(min-width:1280px)",_xxl:"(min-width:1536px)"};function mergeMedia(parent,current){if(!parent)return current;if(!current)return parent;return`${parent} and ${current}`}function serializeNested(style,ctx){let css="",declarations=[];for(let key in style){let value=style[key];if(value==null||typeof value==="object"||key.startsWith("_"))continue;declarations.push(`${toKebabCase(key)}:${value}`)}if(declarations.length){let rule=`${ctx.selector}{${declarations.join(";")}}`;css+=ctx.media?`@media ${ctx.media}{${rule}}`:rule}for(let pseudo of["_hover","_focus","_active"]){let value=style[pseudo];if(!value)continue;css+=serializeNested(value,{selector:`${ctx.selector}:${pseudo.slice(1)}`,media:ctx.media})}for(let key in MEDIA_MAP){let mediaKey=key,value=style[mediaKey];if(!value)continue;css+=serializeNested(value,{selector:ctx.selector,media:mergeMedia(ctx.media,MEDIA_MAP[mediaKey])})}return css}class RelationBuilder{ns;source;pseudo;constructor(ns,source,pseudo){this.ns=ns;this.source=source;this.pseudo=pseudo}hover(){return new RelationBuilder(this.ns,this.source,"hover")}focus(){return new RelationBuilder(this.ns,this.source,"focus")}active(){return new RelationBuilder(this.ns,this.source,"active")}adjacent(target,style){this.emit("+",target,style)}child(target,style){this.emit(">",target,style)}sibling(target,style){this.emit("~",target,style)}descendant(target,style){this.emit(" ",target,style)}emit(combinator,target,style){let pseudo=this.pseudo?`:${this.pseudo}`:"",selector=`.${this.source}${pseudo}${combinator}.${target}`;this.ns.global(selector,style)}}class NextStyle{prefix;rules=new Map;globalStore=new Map;constructor(prefix="next"){this.prefix=prefix}css=(style)=>{let seed=stableStringify(style),hash=createHashName(seed),className=`${this.prefix}_${hash}`,key=`class:${className}`;if(!this.rules.has(key)){let raw=serializeNested(style,{selector:`.${className}`}),cssText=postcssTransform(raw);this.rules.set(key,cssText)}return className};global=(selector,style)=>{let key=`global:${selector}`,merged={...this.globalStore.get(key)??{},...style};this.globalStore.set(key,merged);let raw=serializeNested(merged,{selector}),cssText=postcssTransform(raw);if(this.rules.has(key))this.rules.delete(key);this.rules.set(key,cssText)};resetBrowserStyle=()=>{this.global("html,body",{maxWidth:"100vw",overflowX:"hidden"}),this.global("body",{color:"black",background:"white",fontFamily:"Arial, Helvetica, sans-serif",WebkitFontSmoothing:"antialiased",MozOsxFontSmoothing:"grayscale"}),this.global("*",{boxSizing:"border-box",padding:0,margin:0}),this.global("a",{color:"inherit",textDecoration:"none"})};when=(source)=>new RelationBuilder(this,source);keyframes=(frames)=>{let seed=stableStringify(frames),hash=createHashName(seed),name=`${this.prefix}_${hash}`,key=`@keyframes:${name}`;if(!this.rules.has(key)){let body="";for(let step in frames){let declarations=[],frame=frames[step];for(let prop in frame)declarations.push(`${toKebabCase(prop)}:${frame[prop]}`);body+=`${step}{ ${declarations.join(";")} }`}let cssText=postcssTransform(`@keyframes ${name}{ ${body} }`);this.rules.set(key,cssText)}return name};fontFace=(font)=>{let seed=stableStringify(font),key=`@font-face:${createHashName(seed)}`;if(!this.rules.has(key)){let declarations=[];for(let prop in font)declarations.push(`${toKebabCase(prop)}:${font[prop]}`);let cssText=postcssTransform(`@font-face{ ${declarations.join(";")} }`);this.rules.set(key,cssText)}};toTextCss=()=>{if(this.rules.size===0)return null;let cssText="";for(let rule of this.rules.values())cssText+=rule+`
1
+ import postcss from"postcss";import{createElement}from"react";import autoprefixer from"autoprefixer";var processor=postcss([autoprefixer({overrideBrowserslist:[">0.2%","not dead","not op_mini all"]})]),postcssCache=new Map;function postcssTransform(cssText){let cached=postcssCache.get(cssText);if(cached)return cached;let result=processor.process(cssText,{from:void 0}).css;return postcssCache.set(cssText,result),result}function stableStringify(value){if(value==null||typeof value!=="object")return JSON.stringify(value);if(Array.isArray(value))return`[${value.map(stableStringify).join(",")}]`;return`{${Object.keys(value).sort().map((k)=>`"${k}":${stableStringify(value[k])}`).join(",")}}`}function createHashName(seed){let hash=BigInt("0xcbf29ce484222325"),prime=BigInt("0x100000001b3");for(let i=0;i<seed.length;i++)hash^=BigInt(seed.charCodeAt(i)),hash*=prime,hash&=BigInt("0xffffffffffffffff");return hash.toString(36).slice(0,9)}function toKebabCase(prop){return prop.replace(/[A-Z]/g,(m)=>`-${m.toLowerCase()}`)}var MEDIA_MAP={_sm:"(min-width:640px)",_md:"(min-width:768px)",_lg:"(min-width:1024px)",_xl:"(min-width:1280px)",_xxl:"(min-width:1536px)"};function mergeMedia(parent,current){if(!parent)return current;if(!current)return parent;return`${parent} and ${current}`}function serializeNested(style,ctx){let css="",declarations=[];for(let key in style){let value=style[key];if(value==null||typeof value==="object"||key.startsWith("_"))continue;declarations.push(`${toKebabCase(key)}:${value}`)}if(declarations.length){let rule=`${ctx.selector}{${declarations.join(";")}}`;css+=ctx.media?`@media ${ctx.media}{${rule}}`:rule}for(let pseudo of["_hover","_focus","_active"]){let value=style[pseudo];if(!value)continue;css+=serializeNested(value,{selector:`${ctx.selector}:${pseudo.slice(1)}`,media:ctx.media})}for(let key in MEDIA_MAP){let mediaKey=key,value=style[mediaKey];if(!value)continue;css+=serializeNested(value,{selector:ctx.selector,media:mergeMedia(ctx.media,MEDIA_MAP[mediaKey])})}return css}class RelationBuilder{ns;source;pseudo;constructor(ns,source,pseudo){this.ns=ns;this.source=source;this.pseudo=pseudo}hover(){return new RelationBuilder(this.ns,this.source,"hover")}focus(){return new RelationBuilder(this.ns,this.source,"focus")}active(){return new RelationBuilder(this.ns,this.source,"active")}adjacent(target,style){this.emit("+",target,style)}child(target,style){this.emit(">",target,style)}sibling(target,style){this.emit("~",target,style)}descendant(target,style){this.emit(" ",target,style)}emit(combinator,target,style){let pseudo=this.pseudo?`:${this.pseudo}`:"",selector=`.${this.source}${pseudo}${combinator}.${target}`;this.ns.global(selector,style)}}class NextStyle{prefix;rules=new Map;globalStore=new Map;constructor(prefix="next"){this.prefix=prefix}css=(style)=>{let seed=stableStringify(style),hash=createHashName(seed),className=`${this.prefix}_${hash}`,key=`class:${className}`;if(!this.rules.has(key)){let raw=serializeNested(style,{selector:`.${className}`}),cssText=postcssTransform(raw);this.rules.set(key,cssText)}return className};global=(selector,style)=>{let key=`global:${selector}`,merged={...this.globalStore.get(key)??{},...style};this.globalStore.set(key,merged);let raw=serializeNested(merged,{selector}),cssText=postcssTransform(raw);if(this.rules.has(key))this.rules.delete(key);this.rules.set(key,cssText)};root=(style)=>{this.global(":root",style)};resetStyle=()=>{return this.global("html,body",{maxWidth:"100vw",overflowX:"hidden"}),this.global("body",{color:"black",background:"white",MozOsxFontSmoothing:"grayscale",WebkitFontSmoothing:"antialiased",fontFamily:"Arial, Helvetica, sans-serif"}),this.global("*,*::before,*::after",{margin:0,padding:0,boxSizing:"border-box"}),this.global("a",{color:"inherit",textDecoration:"none"}),this};when=(source)=>new RelationBuilder(this,source);keyframes=(frames)=>{let seed=stableStringify(frames),hash=createHashName(seed),name=`${this.prefix}_${hash}`,key=`@keyframes:${name}`;if(!this.rules.has(key)){let body="";for(let step in frames){let declarations=[],frame=frames[step];for(let prop in frame)declarations.push(`${toKebabCase(prop)}:${frame[prop]}`);body+=`${step}{ ${declarations.join(";")} }`}let cssText=postcssTransform(`@keyframes ${name}{ ${body} }`);this.rules.set(key,cssText)}return name};fontFace=(font)=>{let seed=stableStringify(font),key=`@font-face:${createHashName(seed)}`;if(!this.rules.has(key)){let declarations=[];for(let prop in font)declarations.push(`${toKebabCase(prop)}:${font[prop]}`);let cssText=postcssTransform(`@font-face{ ${declarations.join(";")} }`);this.rules.set(key,cssText)}};toTextCss=()=>{if(this.rules.size===0)return null;let cssText="";for(let rule of this.rules.values())cssText+=rule+`
2
2
  `;return cssText};StyleProvider=()=>{if(this.rules.size===0)return null;let cssText="";for(let rule of this.rules.values())cssText+=rule+`
3
3
  `;return createElement("style",{children:cssText})}}export{NextStyle};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-style",
3
- "version": "1.1.9",
3
+ "version": "1.2.1",
4
4
  "description": "Lightweight runtime CSS-in-JS engine with nested pseudo selectors, media queries, global styles, keyframes, and font-face support.",
5
5
  "author": {
6
6
  "name": "kingslimes",