serpentine-border 1.0.0 → 1.0.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
@@ -54,10 +54,6 @@ Returns `wrapperStyle`, `svgAttributes` (class, viewBox, style), and `paths`. Pa
54
54
  | `layoutMode` | `'content' \| 'border'` | `'content'` | `'content'`: layout from content; `'border'`: outer border edge defines box. |
55
55
  | `svgClassName` | `string` | `'serpentine-border-svg'` | Class applied to the SVG (and used to exclude it when measuring). |
56
56
 
57
- ### measureSections(wrapperEl, options)
58
-
59
- Measures a wrapper and its section children; returns `{ width, sectionBottomYs }` or `null`. Options: `layoutMode`, `horizontalOverlap`, `strokeCount`, `strokeWidth`, and optional `excludeClassName` (default: same as `serpentineBorder`’s `svgClassName`). Use when you want to measure once and pass dimensions into `serpentineBorder`, or in environments without a DOM.
60
-
61
57
  ### React: SerpentineBorder
62
58
 
63
59
  Wrap your content with the React component; it accepts the same options as `serpentineBorder` as props.
@@ -81,31 +77,9 @@ function App() {
81
77
  }
82
78
  ```
83
79
 
84
- ## Explicit dimensions and SSR
85
-
86
- When you need to measure once and reuse, or run without a DOM (SSR, workers), use `measureSections` then call `serpentineBorder` with `width` and `sectionBottomYs`:
87
-
88
- ```js
89
- import { measureSections, serpentineBorder } from 'serpentine-border'
80
+ ## SSR
90
81
 
91
- const wrapper = document.getElementById('wrapper')
92
- const measured = measureSections(wrapper, {
93
- layoutMode: 'content',
94
- horizontalOverlap: 20,
95
- strokeCount: 5,
96
- strokeWidth: 8,
97
- })
98
- if (measured) {
99
- const result = serpentineBorder({
100
- width: measured.width,
101
- sectionBottomYs: measured.sectionBottomYs,
102
- horizontalOverlap: 20,
103
- })
104
- if (result) {
105
- // Apply result.wrapperStyle, result.svgAttributes, result.paths
106
- }
107
- }
108
- ```
82
+ When DOM is unavailable (e.g. server-side), pass `width` and `sectionBottomYs` into `serpentineBorder({ width, sectionBottomYs, ... })` instead of `wrapperEl`.
109
83
 
110
84
  ## License
111
85
 
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const W=require("react/jsx-runtime"),z=require("react"),J=["#ffffff","#000000"];function G(t,c,o){if(typeof t=="number")return t;const s=c*o;return t==="borderWidth"?s:t==="halfBorderWidth"?s/2:0}function Q(t,c,o,s,u,g,l,n,r,h){const e=u*(o-1),a=u/2,$=c.length-1,i=[];for(let f=0;f<o;f++){const d=f*u,m=(o-1-f)*u,v=s-d,B=s-m,C=e-m,k=r-h+a,x=d-r+k,L=h,b=t+L-m-a,y=x+(s-d),N=t+L-s-a,A=t+L-e-a,D=r+n,p=[`M ${b} ${D-e-u/2-l}`,`L ${b} ${D-e-l}`,`A ${C} ${C} 0 0 1 ${A} ${D-m-l}`,`L ${y} ${d+n-l}`,`A ${v} ${v} 0 0 0 ${x} ${s+n-l}`,`L ${x} ${s+n}`];for(let M=0;M<$-1;M++){const w=c[M+1],X=c[M+2],q=w+s-r+n;M%2===0?(p.push(`L ${x} ${w-s+n}`),p.push(`A ${v} ${v} 0 0 0 ${y} ${w-d+n}`),p.push(`L ${N} ${w-d+n}`),p.push(`A ${B} ${B} 0 0 1 ${b} ${q}`),p.push(`L ${b} ${X-s+n}`)):(p.push(`L ${b} ${w-s+n}`),p.push(`A ${B} ${B} 0 0 1 ${N} ${w-m+n}`),p.push(`L ${y} ${w-m+n}`),p.push(`A ${v} ${v} 0 0 0 ${x} ${q}`),p.push(`L ${x} ${X-s+n}`))}const U=c[$];($-2)%2===0?p.push(`L ${b} ${U}`):p.push(`L ${x} ${U}`),i.push({d:p.join(" "),stroke:g[f%g.length],strokeWidth:u,fill:"none"})}return i}const P="serpentine-border-svg",j={strokeCount:5,strokeWidth:8,radius:50,horizontalOverlap:0,layoutMode:"content"};function V(t){const c=t.strokeCount??j.strokeCount,o=t.strokeWidth??j.strokeWidth,s=t.radius??j.radius,u=t.horizontalOverlap??j.horizontalOverlap,g=t.colors??J,l=t.layoutMode??j.layoutMode,n=t.svgClassName??P;let r,h;if(t.wrapperEl!=null){const y=t.wrapperEl;if(!(typeof document<"u"&&typeof y.getBoundingClientRect=="function"))return null;const A=R(y,{layoutMode:l,horizontalOverlap:u,strokeCount:c,strokeWidth:o,excludeClassName:n});if(!A)return null;r=A.width,h=A.sectionBottomYs}else{if(t.width==null||t.sectionBottomYs==null)return null;r=t.width,h=t.sectionBottomYs}const e=G(u,c,o),a=(c-1)*o,$=c*o,i=2*o,f=a/2,d=(c-1)/2*o+f,S=l==="border"?{boxSizing:"border-box",position:"relative",marginTop:$/2,...e>0&&{paddingLeft:e,paddingRight:e}}:{position:"relative",boxSizing:"border-box"},m=l==="border"?{position:"absolute",overflow:"hidden",width:"100%",left:0,top:-(i+d),height:`calc(100% + ${i+d}px)`}:{position:"absolute",overflow:"hidden",width:`calc(100% + ${2*e}px)`,left:-e,top:-(i+d),height:`calc(100% + ${i+d}px)`},v=Q(r,h,c,s,o,g,d,f,a,e),B=h[h.length-1]??0,C=Math.max(1,r+2*e),k=B+i+d,x=e>0?-e:0,L=-o*2-d,b=`${x} ${L} ${C} ${k}`;return{wrapperStyle:S,svgAttributes:{class:n,viewBox:b,style:m},paths:v}}function R(t,c){const{layoutMode:o,horizontalOverlap:s=0,strokeCount:u,strokeWidth:g,excludeClassName:l=P}=c,n=G(s,u,g);if(!t)return null;const r=l?Array.from(t.children).filter(i=>!i.classList.contains(l)):Array.from(t.children);if(r.length===0)return null;const h=t.getBoundingClientRect(),e=h.width,a=o==="border"?Math.max(1,e-2*n):Math.max(1,e),$=[0];for(let i=0;i<r.length;i++){const f=r[i].getBoundingClientRect();$.push(f.top-h.top+f.height)}return{width:a,sectionBottomYs:$}}function Z({children:t,strokeCount:c,strokeWidth:o,radius:s,horizontalOverlap:u,colors:g,layoutMode:l}){const n=z.useRef(null),[r,h]=z.useState(null);return z.useEffect(()=>{const e=n.current;if(!e)return;const a=()=>{const i=R(e,{layoutMode:l,horizontalOverlap:u,strokeCount:c,strokeWidth:o});if(!i)return;const f=V({width:i.width,sectionBottomYs:i.sectionBottomYs,strokeCount:c,strokeWidth:o,radius:s,horizontalOverlap:u,colors:g,layoutMode:l});h(f)};a();const $=new ResizeObserver(a);return $.observe(e),()=>$.disconnect()},[c,o,s,u,g,l]),W.jsxs("div",{ref:n,className:"serpentine-wrapper",style:(r==null?void 0:r.wrapperStyle)??{position:"relative",boxSizing:"border-box"},"data-testid":"serpentine-wrapper",children:[r&&(()=>{const{class:e,...a}=r.svgAttributes;return W.jsx("svg",{"data-testid":"serpentine-svg",className:e,...a,children:r.paths.map(($,i)=>W.jsx("path",{...$},i))})})(),t]})}exports.SerpentineBorder=Z;exports.measureSections=R;exports.serpentineBorder=V;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const W=require("react/jsx-runtime"),z=require("react"),J=["#ffffff","#000000"];function G(t,c,o){if(typeof t=="number")return t;const s=c*o;return t==="borderWidth"?s:t==="halfBorderWidth"?s/2:0}function Q(t,c,o,s,u,g,l,n,r,h){const e=u*(o-1),a=u/2,$=c.length-1,i=[];for(let f=0;f<o;f++){const d=f*u,m=(o-1-f)*u,v=s-d,B=s-m,C=e-m,k=r-h+a,x=d-r+k,L=h,b=t+L-m-a,y=x+(s-d),N=t+L-s-a,A=t+L-e-a,D=r+n,p=[`M ${b} ${D-e-u/2-l}`,`L ${b} ${D-e-l}`,`A ${C} ${C} 0 0 1 ${A} ${D-m-l}`,`L ${y} ${d+n-l}`,`A ${v} ${v} 0 0 0 ${x} ${s+n-l}`,`L ${x} ${s+n}`];for(let M=0;M<$-1;M++){const w=c[M+1],X=c[M+2],q=w+s-r+n;M%2===0?(p.push(`L ${x} ${w-s+n}`),p.push(`A ${v} ${v} 0 0 0 ${y} ${w-d+n}`),p.push(`L ${N} ${w-d+n}`),p.push(`A ${B} ${B} 0 0 1 ${b} ${q}`),p.push(`L ${b} ${X-s+n}`)):(p.push(`L ${b} ${w-s+n}`),p.push(`A ${B} ${B} 0 0 1 ${N} ${w-m+n}`),p.push(`L ${y} ${w-m+n}`),p.push(`A ${v} ${v} 0 0 0 ${x} ${q}`),p.push(`L ${x} ${X-s+n}`))}const U=c[$];($-2)%2===0?p.push(`L ${b} ${U}`):p.push(`L ${x} ${U}`),i.push({d:p.join(" "),stroke:g[f%g.length],strokeWidth:u,fill:"none"})}return i}const S="serpentine-border-svg",j={strokeCount:5,strokeWidth:8,radius:50,horizontalOverlap:0,layoutMode:"content"};function P(t){const c=t.strokeCount??j.strokeCount,o=t.strokeWidth??j.strokeWidth,s=t.radius??j.radius,u=t.horizontalOverlap??j.horizontalOverlap,g=t.colors??J,l=t.layoutMode??j.layoutMode,n=t.svgClassName??S;let r,h;if(t.wrapperEl!=null){const y=t.wrapperEl;if(!(typeof document<"u"&&typeof y.getBoundingClientRect=="function"))return null;const A=V(y,{layoutMode:l,horizontalOverlap:u,strokeCount:c,strokeWidth:o,excludeClassName:n});if(!A)return null;r=A.width,h=A.sectionBottomYs}else{if(t.width==null||t.sectionBottomYs==null)return null;r=t.width,h=t.sectionBottomYs}const e=G(u,c,o),a=(c-1)*o,$=c*o,i=2*o,f=a/2,d=(c-1)/2*o+f,R=l==="border"?{boxSizing:"border-box",position:"relative",marginTop:$/2,...e>0&&{paddingLeft:e,paddingRight:e}}:{position:"relative",boxSizing:"border-box"},m=l==="border"?{position:"absolute",overflow:"hidden",width:"100%",left:0,top:-(i+d),height:`calc(100% + ${i+d}px)`}:{position:"absolute",overflow:"hidden",width:`calc(100% + ${2*e}px)`,left:-e,top:-(i+d),height:`calc(100% + ${i+d}px)`},v=Q(r,h,c,s,o,g,d,f,a,e),B=h[h.length-1]??0,C=Math.max(1,r+2*e),k=B+i+d,x=e>0?-e:0,L=-o*2-d,b=`${x} ${L} ${C} ${k}`;return{wrapperStyle:R,svgAttributes:{class:n,viewBox:b,style:m},paths:v}}function V(t,c){const{layoutMode:o,horizontalOverlap:s=0,strokeCount:u,strokeWidth:g,excludeClassName:l=S}=c,n=G(s,u,g);if(!t)return null;const r=l?Array.from(t.children).filter(i=>!i.classList.contains(l)):Array.from(t.children);if(r.length===0)return null;const h=t.getBoundingClientRect(),e=h.width,a=o==="border"?Math.max(1,e-2*n):Math.max(1,e),$=[0];for(let i=0;i<r.length;i++){const f=r[i].getBoundingClientRect();$.push(f.top-h.top+f.height)}return{width:a,sectionBottomYs:$}}function Z({children:t,strokeCount:c,strokeWidth:o,radius:s,horizontalOverlap:u,colors:g,layoutMode:l}){const n=z.useRef(null),[r,h]=z.useState(null);return z.useEffect(()=>{const e=n.current;if(!e)return;const a=()=>{const i=V(e,{layoutMode:l,horizontalOverlap:u,strokeCount:c,strokeWidth:o});if(!i)return;const f=P({width:i.width,sectionBottomYs:i.sectionBottomYs,strokeCount:c,strokeWidth:o,radius:s,horizontalOverlap:u,colors:g,layoutMode:l});h(f)};a();const $=new ResizeObserver(a);return $.observe(e),()=>$.disconnect()},[c,o,s,u,g,l]),W.jsxs("div",{ref:n,className:"serpentine-wrapper",style:(r==null?void 0:r.wrapperStyle)??{position:"relative",boxSizing:"border-box"},"data-testid":"serpentine-wrapper",children:[r&&(()=>{const{class:e,...a}=r.svgAttributes;return W.jsx("svg",{"data-testid":"serpentine-svg",className:e,...a,children:r.paths.map(($,i)=>W.jsx("path",{...$},i))})})(),t]})}exports.SerpentineBorder=Z;exports.serpentineBorder=P;
2
2
  //# sourceMappingURL=serpentine-border.cjs.map
@@ -168,7 +168,6 @@ function I({
168
168
  }
169
169
  export {
170
170
  I as SerpentineBorder,
171
- q as measureSections,
172
171
  H as serpentineBorder
173
172
  };
174
173
  //# sourceMappingURL=serpentine-border.js.map
package/index.d.ts CHANGED
@@ -36,14 +36,3 @@ export interface SerpentineBorderResult {
36
36
  }
37
37
 
38
38
  export declare function serpentineBorder(options: SerpentineBorderOptions): SerpentineBorderResult | null
39
-
40
- export declare function measureSections(
41
- wrapperEl: HTMLElement,
42
- options: {
43
- layoutMode: 'content' | 'border'
44
- horizontalOverlap?: number | 'borderWidth' | 'halfBorderWidth'
45
- strokeCount: number
46
- strokeWidth: number
47
- excludeClassName?: string
48
- }
49
- ): { width: number; sectionBottomYs: number[] } | null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serpentine-border",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Multi-stroke serpentine (wavy) border SVG — vanilla JS and React",
5
5
  "type": "module",
6
6
  "main": "./dist/serpentine-border.cjs",