serpentine-border 2.0.0 → 3.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
@@ -25,9 +25,9 @@ function setAttributes(el, attrs) {
25
25
  const wrapperEl = document.getElementById('wrapper')
26
26
  const result = serpentineBorder({ wrapperEl })
27
27
  if (!result) return
28
- const { wrapperStyle, svgAttributes, paths, sectionsPadding } = result
28
+ const { wrapperStyle, svgAttributes, paths } = result
29
29
  Object.assign(wrapperEl.style, wrapperStyle)
30
- // Optionally apply sectionsPadding[i] to each section so content does not overlap the border
30
+ // Optionally: getSectionsPadding({ sectionCount: n, strokeCount, strokeWidth, horizontalOverflow }) for per-section padding
31
31
 
32
32
  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
33
33
  setAttributes(svg, svgAttributes)
@@ -44,7 +44,7 @@ wrapperEl.insertBefore(svg, wrapperEl.firstChild)
44
44
 
45
45
  ### serpentineBorder(options)
46
46
 
47
- Returns `wrapperStyle`, `svgAttributes` (class, viewBox, style), `paths`, and `sectionsPadding` (array of `{ top, right, bottom, left }` padding in px for each section so content does not overlap the border). Pass either `wrapperEl` (measures from the DOM; returns `null` when DOM is unavailable, e.g. SSR) or `width` + `sectionBottomYs` (pure; never returns null).
47
+ Returns `wrapperStyle`, `svgAttributes` (class, viewBox, style), and `paths`. Pass either `wrapperEl` (measures from the DOM; returns `null` when DOM is unavailable, e.g. SSR) or `width` + `sectionBottomYs` (pure; never returns null). Use **`getSectionsPadding({ sectionCount, strokeCount, strokeWidth, horizontalOverflow })`** for `{ even, odd, last }` — padding objects for even-indexed sections, odd-indexed sections, and the last section so content does not overlap the border.
48
48
 
49
49
  | Option | Type | Default | Description |
50
50
  |--------|------|---------|-------------|
@@ -61,6 +61,17 @@ Returns `wrapperStyle`, `svgAttributes` (class, viewBox, style), `paths`, and `s
61
61
 
62
62
  **Layout mode:** In some instances, you may want the border to be an overlay that doesn't affect flow and content size. With `'content'`, the wrapper’s size follows its content and the border is drawn around it (the SVG can extend outside). With `'border'`, the outer edge of the border defines the box: the full border fits inside the layout, and content sits inside that box. This mode avoids the border spilling out and overlapping neighboring elements.
63
63
 
64
+ ### getSectionsPadding(options)
65
+
66
+ Returns `{ even, odd, last }` — each is a `{ top, right, bottom, left }` (px) padding object. Use `even` for even-indexed sections (0, 2, …), `odd` for odd-indexed sections (1, 3, …), and `last` for the final section (which has `bottom: 0`). Use the same `strokeCount`, `strokeWidth`, and `horizontalOverflow` as your border; `sectionCount` is the number of sections (e.g. `sectionBottomYs.length - 1`). Handy with the React component: call with the section count and props, then apply `even`/`odd`/`last` to the corresponding section elements.
67
+
68
+ | Option | Type | Description |
69
+ |--------|------|-------------|
70
+ | `sectionCount` | `number` | Number of sections. |
71
+ | `strokeCount` | `number` | Same as serpentineBorder. |
72
+ | `strokeWidth` | `number` | Same as serpentineBorder. |
73
+ | `horizontalOverflow` | `number \| 'borderWidth' \| 'halfBorderWidth'` | Same as serpentineBorder. |
74
+
64
75
  ### React: SerpentineBorder
65
76
 
66
77
  Wrap your content with the React component; it accepts the same options as `serpentineBorder` as props.
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const R=require("react/jsx-runtime"),U=require("react"),Z=["#ffffff","#000000"];function q(t,n,e){if(typeof t=="number")return t;const s=n*e;return t==="borderWidth"?s:t==="halfBorderWidth"?s/2:0}function J(t){return Object.entries(t).map(([n,e])=>{const s=n.replace(/([A-Z])/g,"-$1").toLowerCase(),c=typeof e=="number"&&!Number.isNaN(e)?`${e}px`:String(e);return`${s}: ${c}`}).join("; ")}function Q(t,n,e,s,c,x,u,r,i,$){const o=c*(e-1),a=c/2,f=n.length-1,l=[];for(let p=0;p<e;p++){const d=p*c,y=(e-1-p)*c,w=s-d,L=s-y,N=o-y,z=i-$+a,m=d-i+z,A=$,v=t+A-y-a,C=m+(s-d),M=t+A-s-a,S=t+A-o-a,j=i+r,h=[`M ${v} ${j-o-c/2-u}`,`L ${v} ${j-o-u}`,`A ${N} ${N} 0 0 1 ${S} ${j-y-u}`,`L ${C} ${d+r-u}`,`A ${w} ${w} 0 0 0 ${m} ${s+r-u}`,`L ${m} ${s+r}`];for(let B=0;B<f-1;B++){const g=n[B+1],W=n[B+2],D=g+s-i+r;B%2===0?(h.push(`L ${m} ${g-s+r}`),h.push(`A ${w} ${w} 0 0 0 ${C} ${g-d+r}`),h.push(`L ${M} ${g-d+r}`),h.push(`A ${L} ${L} 0 0 1 ${v} ${D}`),h.push(`L ${v} ${W-s+r}`)):(h.push(`L ${v} ${g-s+r}`),h.push(`A ${L} ${L} 0 0 1 ${M} ${g-y+r}`),h.push(`L ${C} ${g-y+r}`),h.push(`A ${w} ${w} 0 0 0 ${m} ${D}`),h.push(`L ${m} ${W-s+r}`))}const b=n[f];(f-2)%2===0?h.push(`L ${v} ${b}`):h.push(`L ${m} ${b}`),l.push({d:h.join(" "),stroke:x[p%x.length],"stroke-width":c,fill:"none"})}return l}const G="serpentine-border-svg",k={strokeCount:5,strokeWidth:8,radius:50,horizontalOverflow:0,layoutMode:"border"};function P(t){const n=t.strokeCount??k.strokeCount,e=t.strokeWidth??k.strokeWidth,s=t.radius??k.radius,c=t.horizontalOverflow??k.horizontalOverflow,x=t.colors??Z,u=t.layoutMode??k.layoutMode,r=t.svgClassName??G;let i,$;if(t.wrapperEl!=null){const b=t.wrapperEl;if(!(typeof document<"u"&&typeof b.getBoundingClientRect=="function"))return null;const g=V(b,{layoutMode:u,horizontalOverflow:c,strokeCount:n,strokeWidth:e,excludeClassName:r});if(!g)return null;i=g.width,$=g.sectionBottomYs}else{if(t.width==null||t.sectionBottomYs==null)return null;i=t.width,$=t.sectionBottomYs}const o=q(c,n,e),a=(n-1)*e,f=n*e,l=2*e,p=a/2,d=(n-1)/2*e+p,X=u==="border"?{boxSizing:"border-box",position:"relative",marginTop:`${f/2}px`,...o>0&&{paddingLeft:`${o}px`,paddingRight:`${o}px`}}:{position:"relative",boxSizing:"border-box"},y=u==="border"?{position:"absolute",overflow:"hidden",width:"100%",left:0,top:-(l+d),height:`calc(100% + ${l+d}px)`}:{position:"absolute",overflow:"hidden",width:`calc(100% + ${2*o}px)`,left:-o,top:-(l+d),height:`calc(100% + ${l+d}px)`},w=J(y),L=Q(i,$,n,s,e,x,d,p,a,o),N=$[$.length-1]??0,z=Math.max(1,i+2*o),m=N+l+d,A=o>0?-o:0,v=-e*2-d,C=`${A} ${v} ${z} ${m}`,M=$.length-1,S=f/2,j=f-o,h=[];for(let b=0;b<M;b++){const B=S,g=b===M-1?0:S,W=b%2===0?j:0,D=b%2===0?0:j;h.push({top:B,right:D,bottom:g,left:W})}return{wrapperStyle:X,svgAttributes:{class:r,viewBox:C,style:w},paths:L,sectionsPadding:h}}function V(t,n){const{layoutMode:e,horizontalOverflow:s=0,strokeCount:c,strokeWidth:x,excludeClassName:u=G}=n,r=q(s,c,x);if(!t)return null;const i=u?Array.from(t.children).filter(l=>!l.classList.contains(u)):Array.from(t.children);if(i.length===0)return null;const $=t.getBoundingClientRect(),o=$.width,a=e==="border"?Math.max(1,o-2*r):Math.max(1,o),f=[0];for(let l=0;l<i.length;l++){const p=i[l].getBoundingClientRect();f.push(p.top-$.top+p.height)}return{width:a,sectionBottomYs:f}}function E(t){const n={};if(!t||typeof t!="string")return n;for(const e of t.split(";")){const s=e.indexOf(":");if(s===-1)continue;const c=e.slice(0,s).trim().replace(/-([a-z])/g,(u,r)=>r.toUpperCase()),x=e.slice(s+1).trim();c&&(n[c]=x)}return n}function O(t){return Object.fromEntries(Object.entries(t).map(([n,e])=>[n==="stroke-width"?"strokeWidth":n,e]))}function H({children:t,strokeCount:n,strokeWidth:e,radius:s,horizontalOverflow:c,colors:x,layoutMode:u}){const r=U.useRef(null),[i,$]=U.useState(null);return U.useEffect(()=>{const o=r.current;if(!o)return;const a=()=>{const l=V(o,{layoutMode:u,horizontalOverflow:c,strokeCount:n,strokeWidth:e});if(!l)return;const p=P({width:l.width,sectionBottomYs:l.sectionBottomYs,strokeCount:n,strokeWidth:e,radius:s,horizontalOverflow:c,colors:x,layoutMode:u});$(p)};a();const f=new ResizeObserver(a);return f.observe(o),()=>f.disconnect()},[n,e,s,c,x,u]),R.jsxs("div",{ref:r,className:"serpentine-wrapper",style:(i==null?void 0:i.wrapperStyle)??{position:"relative",boxSizing:"border-box"},"data-testid":"serpentine-wrapper",children:[i&&(()=>{const{class:o,style:a,...f}=i.svgAttributes,l=typeof a=="string"?E(a):a;return R.jsx("svg",{"data-testid":"serpentine-svg",className:o,style:l,...f,children:i.paths.map((p,d)=>R.jsx("path",{...O(p)},d))})})(),t]})}exports.SerpentineBorder=H;exports.serpentineBorder=P;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const R=require("react/jsx-runtime"),S=require("react"),G=["#ffffff","#000000"];function D(t,n,e){if(typeof t=="number")return t;const o=n*e;return t==="borderWidth"?o:t==="halfBorderWidth"?o/2:0}function V(t){return Object.entries(t).map(([n,e])=>{const o=n.replace(/([A-Z])/g,"-$1").toLowerCase(),c=typeof e=="number"&&!Number.isNaN(e)?`${e}px`:String(e);return`${o}: ${c}`}).join("; ")}function Z(t,n,e,o,c,p,u,s,i,h){const r=c*(e-1),a=c/2,$=n.length-1,l=[];for(let f=0;f<e;f++){const d=f*c,b=(e-1-f)*c,m=o-d,B=o-b,M=r-b,N=i-h+a,x=d-i+N,L=h,w=t+L-b-a,j=x+(o-d),A=t+L-o-a,z=t+L-r-a,y=i+s,g=[`M ${w} ${y-r-c/2-u}`,`L ${w} ${y-r-u}`,`A ${M} ${M} 0 0 1 ${z} ${y-b-u}`,`L ${j} ${d+s-u}`,`A ${m} ${m} 0 0 0 ${x} ${o+s-u}`,`L ${x} ${o+s}`];for(let C=0;C<$-1;C++){const v=n[C+1],O=n[C+2],U=v+o-i+s;C%2===0?(g.push(`L ${x} ${v-o+s}`),g.push(`A ${m} ${m} 0 0 0 ${j} ${v-d+s}`),g.push(`L ${A} ${v-d+s}`),g.push(`A ${B} ${B} 0 0 1 ${w} ${U}`),g.push(`L ${w} ${O-o+s}`)):(g.push(`L ${w} ${v-o+s}`),g.push(`A ${B} ${B} 0 0 1 ${A} ${v-b+s}`),g.push(`L ${j} ${v-b+s}`),g.push(`A ${m} ${m} 0 0 0 ${x} ${U}`),g.push(`L ${x} ${O-o+s}`))}const E=n[$];($-2)%2===0?g.push(`L ${w} ${E}`):g.push(`L ${x} ${E}`),l.push({d:g.join(" "),stroke:p[f%p.length],"stroke-width":c,fill:"none"})}return l}const X="serpentine-border-svg",k={strokeCount:5,strokeWidth:8,radius:50,horizontalOverflow:0,layoutMode:"border"};function P(t){const n=t.strokeCount??k.strokeCount,e=t.strokeWidth??k.strokeWidth,o=t.radius??k.radius,c=t.horizontalOverflow??k.horizontalOverflow,p=t.colors??G,u=t.layoutMode??k.layoutMode,s=t.svgClassName??X;let i,h;if(t.wrapperEl!=null){const A=t.wrapperEl;if(!(typeof document<"u"&&typeof A.getBoundingClientRect=="function"))return null;const y=q(A,{layoutMode:u,horizontalOverflow:c,strokeCount:n,strokeWidth:e,excludeClassName:s});if(!y)return null;i=y.width,h=y.sectionBottomYs}else{if(t.width==null||t.sectionBottomYs==null)return null;i=t.width,h=t.sectionBottomYs}const r=D(c,n,e),a=(n-1)*e,$=n*e,l=2*e,f=a/2,d=(n-1)/2*e+f,W=u==="border"?{boxSizing:"border-box",position:"relative",marginTop:`${$/2}px`,...r>0&&{paddingLeft:`${r}px`,paddingRight:`${r}px`}}:{position:"relative",boxSizing:"border-box"},b=u==="border"?{position:"absolute",overflow:"hidden",...r>0?{width:"100%",left:0}:{width:`calc(100% + ${2*r}px)`,left:-r},top:-(l+d),height:`calc(100% + ${l+d}px)`}:{position:"absolute",overflow:"hidden",width:`calc(100% + ${2*r}px)`,left:-r,top:-(l+d),height:`calc(100% + ${l+d}px)`},m=V(b),B=Z(i,h,n,o,e,p,d,f,a,r),M=h[h.length-1]??0,N=Math.max(1,i+2*r),x=M+l+d,L=r>0?-r:0,w=-e*2-d,j=`${L} ${w} ${N} ${x}`;return{wrapperStyle:W,svgAttributes:{class:s,viewBox:j,style:m},paths:B}}function J(t){const{sectionCount:n,strokeCount:e,strokeWidth:o}=t,c=t.horizontalOverflow??0,p=e*o,u=D(c,e,o),s=p/2,i=p-u,h={top:s,right:0,bottom:s,left:i},r={top:s,right:i,bottom:s,left:0},a=n>0&&(n-1)%2===0;return{even:h,odd:r,last:{top:s,right:a?0:i,bottom:0,left:a?i:0}}}function q(t,n){const{layoutMode:e,horizontalOverflow:o=0,strokeCount:c,strokeWidth:p,excludeClassName:u=X}=n,s=D(o,c,p);if(!t)return null;const i=u?Array.from(t.children).filter(l=>!l.classList.contains(u)):Array.from(t.children);if(i.length===0)return null;const h=t.getBoundingClientRect(),r=h.width,a=e==="border"?Math.max(1,r-2*s):Math.max(1,r),$=[0];for(let l=0;l<i.length;l++){const f=i[l].getBoundingClientRect();$.push(f.top-h.top+f.height)}return{width:a,sectionBottomYs:$}}function Q(t){const n={};if(!t||typeof t!="string")return n;for(const e of t.split(";")){const o=e.indexOf(":");if(o===-1)continue;const c=e.slice(0,o).trim().replace(/-([a-z])/g,(u,s)=>s.toUpperCase()),p=e.slice(o+1).trim();c&&(n[c]=p)}return n}function H(t){return Object.fromEntries(Object.entries(t).map(([n,e])=>[n==="stroke-width"?"strokeWidth":n,e]))}function I({children:t,strokeCount:n,strokeWidth:e,radius:o,horizontalOverflow:c,colors:p,layoutMode:u}){const s=S.useRef(null),[i,h]=S.useState(null);return S.useEffect(()=>{const r=s.current;if(!r)return;const a=()=>{const l=q(r,{layoutMode:u,horizontalOverflow:c,strokeCount:n,strokeWidth:e});if(!l)return;const f=P({width:l.width,sectionBottomYs:l.sectionBottomYs,strokeCount:n,strokeWidth:e,radius:o,horizontalOverflow:c,colors:p,layoutMode:u});h(f)};a();const $=new ResizeObserver(a);return $.observe(r),()=>$.disconnect()},[n,e,o,c,p,u]),R.jsxs("div",{ref:s,className:"serpentine-wrapper",style:(i==null?void 0:i.wrapperStyle)??{position:"relative",boxSizing:"border-box"},"data-testid":"serpentine-wrapper",children:[i&&(()=>{const{class:r,style:a,...$}=i.svgAttributes,l=typeof a=="string"?Q(a):a;return R.jsx("svg",{"data-testid":"serpentine-svg",className:r,style:l,...$,children:i.paths.map((f,d)=>R.jsx("path",{...H(f)},d))})})(),t]})}exports.SerpentineBorder=I;exports.getSectionsPadding=J;exports.serpentineBorder=P;
2
2
  //# sourceMappingURL=serpentine-border.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"serpentine-border.cjs","sources":["../src/constants.js","../src/serpentineCore.js","../src/SerpentineBorder.jsx"],"sourcesContent":["export const DEFAULT_COLORS = ['#ffffff', '#000000']\n","/**\n * Vanilla JS core for serpentine border SVG generation.\n * Single export: call with measured dimensions and options to get everything needed to render.\n */\n\nimport { DEFAULT_COLORS } from './constants.js'\n\nfunction resolveOverflowToPixels(horizontalOverflow, N, STROKE_WIDTH) {\n if (typeof horizontalOverflow === 'number') return horizontalOverflow\n const totalBorderWidth = N * STROKE_WIDTH\n if (horizontalOverflow === 'borderWidth') return totalBorderWidth\n if (horizontalOverflow === 'halfBorderWidth') return totalBorderWidth / 2\n return 0\n}\n\nfunction styleObjectToCss(obj) {\n return Object.entries(obj)\n .map(([k, v]) => {\n const key = k.replace(/([A-Z])/g, '-$1').toLowerCase()\n const val = typeof v === 'number' && !Number.isNaN(v) ? `${v}px` : String(v)\n return `${key}: ${val}`\n })\n .join('; ')\n}\n\nfunction buildPathD(W, Y, N, R, STROKE_WIDTH, COLORS, TOP_ARC_SHIFT, Y_OFFSET, O_TOTAL, BORDER_EXTRA) {\n const R1 = STROKE_WIDTH * (N - 1)\n const RIGHT_EXTEND = STROKE_WIDTH / 2\n const n = Y.length - 1\n const parts = []\n for (let i = 0; i < N; i++) {\n const o = i * STROKE_WIDTH\n const j = N - 1 - i\n const oj = j * STROKE_WIDTH\n const r = R - o\n const rj = R - oj\n const r1 = R1 - o\n const rj1 = R1 - oj\n\n const leftOffset = O_TOTAL - BORDER_EXTRA + RIGHT_EXTEND\n const xLeft = o - O_TOTAL + leftOffset\n const rightExt = BORDER_EXTRA\n const xRight = W + rightExt - oj - RIGHT_EXTEND\n const xLeftArc = xLeft + (R - o)\n const xRightArc = W + rightExt - R - RIGHT_EXTEND\n const xRightR1 = W + rightExt - R1 - RIGHT_EXTEND\n\n const yCurrTop = O_TOTAL + Y_OFFSET\n const segs = [\n `M ${xRight} ${yCurrTop - R1 - STROKE_WIDTH / 2 - TOP_ARC_SHIFT}`,\n `L ${xRight} ${yCurrTop - R1 - TOP_ARC_SHIFT}`,\n `A ${rj1} ${rj1} 0 0 1 ${xRightR1} ${yCurrTop - oj - TOP_ARC_SHIFT}`,\n `L ${xLeftArc} ${o + Y_OFFSET - TOP_ARC_SHIFT}`,\n `A ${r} ${r} 0 0 0 ${xLeft} ${R + Y_OFFSET - TOP_ARC_SHIFT}`,\n `L ${xLeft} ${R + Y_OFFSET}`,\n ]\n\n for (let t = 0; t < n - 1; t++) {\n const yCurr = Y[t + 1]\n const yNext = Y[t + 2]\n const yExit = yCurr + R - O_TOTAL + Y_OFFSET\n\n if (t % 2 === 0) {\n segs.push(`L ${xLeft} ${yCurr - R + Y_OFFSET}`)\n segs.push(`A ${r} ${r} 0 0 0 ${xLeftArc} ${yCurr - o + Y_OFFSET}`)\n segs.push(`L ${xRightArc} ${yCurr - o + Y_OFFSET}`)\n segs.push(`A ${rj} ${rj} 0 0 1 ${xRight} ${yExit}`)\n segs.push(`L ${xRight} ${yNext - R + Y_OFFSET}`)\n } else {\n segs.push(`L ${xRight} ${yCurr - R + Y_OFFSET}`)\n segs.push(`A ${rj} ${rj} 0 0 1 ${xRightArc} ${yCurr - oj + Y_OFFSET}`)\n segs.push(`L ${xLeftArc} ${yCurr - oj + Y_OFFSET}`)\n segs.push(`A ${r} ${r} 0 0 0 ${xLeft} ${yExit}`)\n segs.push(`L ${xLeft} ${yNext - R + Y_OFFSET}`)\n }\n }\n\n const lastY = Y[n]\n if ((n - 2) % 2 === 0) {\n segs.push(`L ${xRight} ${lastY}`)\n } else {\n segs.push(`L ${xLeft} ${lastY}`)\n }\n parts.push({\n d: segs.join(' '),\n stroke: COLORS[i % COLORS.length],\n 'stroke-width': STROKE_WIDTH,\n fill: 'none',\n })\n }\n return parts\n}\n\nconst DEFAULT_SVG_CLASS = 'serpentine-border-svg'\n\nconst DEFAULTS = {\n strokeCount: 5,\n strokeWidth: 8,\n radius: 50,\n horizontalOverflow: 0,\n layoutMode: 'border',\n}\n\n/**\n * Compute everything needed to render the serpentine border.\n * Accepts either (width + sectionBottomYs) for pure/custom use, or wrapperEl to measure from the DOM.\n * When using wrapperEl, returns null in non-DOM environments (e.g. SSR) or when measurement fails.\n *\n * @param {{\n * width?: number\n * sectionBottomYs?: number[]\n * wrapperEl?: HTMLElement\n * strokeCount?: number\n * strokeWidth?: number\n * radius?: number\n * horizontalOverflow?: number | 'borderWidth' | 'halfBorderWidth'\n * colors?: string[]\n * layoutMode?: 'content' | 'border'\n * svgClassName?: string\n * }} options\n * @returns {{\n * wrapperStyle: Record<string, unknown>\n * svgAttributes: { class?: string, viewBox: string, style: string }\n * paths: Array<{ d: string, stroke: string, 'stroke-width': number, fill: string }>\n * sectionsPadding: Array<{ top: number, right: number, bottom: number, left: number }>\n * } | null}\n */\nexport function serpentineBorder(options) {\n const N = options.strokeCount ?? DEFAULTS.strokeCount\n const STROKE_WIDTH = options.strokeWidth ?? DEFAULTS.strokeWidth\n const R = options.radius ?? DEFAULTS.radius\n const horizontalOverflow = options.horizontalOverflow ?? DEFAULTS.horizontalOverflow\n const COLORS = options.colors ?? DEFAULT_COLORS\n const layoutMode = options.layoutMode ?? DEFAULTS.layoutMode\n const svgClassName = options.svgClassName ?? DEFAULT_SVG_CLASS\n\n let W, Y\n if (options.wrapperEl != null) {\n const wrapperEl = options.wrapperEl\n const hasDOM = typeof document !== 'undefined' && typeof wrapperEl.getBoundingClientRect === 'function'\n if (!hasDOM) return null\n const measured = measureSections(wrapperEl, {\n layoutMode,\n horizontalOverflow,\n strokeCount: N,\n strokeWidth: STROKE_WIDTH,\n excludeClassName: svgClassName,\n })\n if (!measured) return null\n W = measured.width\n Y = measured.sectionBottomYs\n } else {\n if (options.width == null || options.sectionBottomYs == null) return null\n W = options.width\n Y = options.sectionBottomYs\n }\n\n const BORDER_EXTRA = resolveOverflowToPixels(horizontalOverflow, N, STROKE_WIDTH)\n const O_TOTAL = (N - 1) * STROKE_WIDTH\n const TOTAL_BORDER_WIDTH = N * STROKE_WIDTH\n const TOP_OFFSET = 2 * STROKE_WIDTH\n const Y_OFFSET = O_TOTAL / 2\n const TOP_ARC_SHIFT = ((N - 1) / 2) * STROKE_WIDTH + Y_OFFSET\n\n const wrapperStyle =\n layoutMode === 'border'\n ? {\n boxSizing: 'border-box',\n position: 'relative',\n marginTop: `${TOTAL_BORDER_WIDTH / 2}px`,\n ...(BORDER_EXTRA > 0 && {\n paddingLeft: `${BORDER_EXTRA}px`,\n paddingRight: `${BORDER_EXTRA}px`,\n }),\n }\n : {\n position: 'relative',\n boxSizing: 'border-box',\n }\n\n const svgStyleObj =\n layoutMode === 'border'\n ? {\n position: 'absolute',\n overflow: 'hidden',\n width: '100%',\n left: 0,\n top: -(TOP_OFFSET + TOP_ARC_SHIFT),\n height: `calc(100% + ${TOP_OFFSET + TOP_ARC_SHIFT}px)`,\n }\n : {\n position: 'absolute',\n overflow: 'hidden',\n width: `calc(100% + ${2 * BORDER_EXTRA}px)`,\n left: -BORDER_EXTRA,\n top: -(TOP_OFFSET + TOP_ARC_SHIFT),\n height: `calc(100% + ${TOP_OFFSET + TOP_ARC_SHIFT}px)`,\n }\n const svgStyle = styleObjectToCss(svgStyleObj)\n\n const paths = buildPathD(W, Y, N, R, STROKE_WIDTH, COLORS, TOP_ARC_SHIFT, Y_OFFSET, O_TOTAL, BORDER_EXTRA)\n\n const totalHeight = Y[Y.length - 1] ?? 0\n const totalWidth = Math.max(1, W + 2 * BORDER_EXTRA)\n const viewBoxHeight = totalHeight + TOP_OFFSET + TOP_ARC_SHIFT\n const viewBoxMinX = BORDER_EXTRA > 0 ? -BORDER_EXTRA : 0\n const viewBoxMinY = -STROKE_WIDTH * 2 - TOP_ARC_SHIFT\n const viewBoxStr = `${viewBoxMinX} ${viewBoxMinY} ${totalWidth} ${viewBoxHeight}`\n\n const n = Y.length - 1\n const halfBorderWidth = TOTAL_BORDER_WIDTH / 2\n const insetSide = TOTAL_BORDER_WIDTH - BORDER_EXTRA\n const sectionsPadding = []\n for (let i = 0; i < n; i++) {\n const top = halfBorderWidth\n const bottom = i === n - 1 ? 0 : halfBorderWidth\n const left = i % 2 === 0 ? insetSide : 0\n const right = i % 2 === 0 ? 0 : insetSide\n sectionsPadding.push({ top, right, bottom, left })\n }\n\n return {\n wrapperStyle,\n svgAttributes: {\n class: svgClassName,\n viewBox: viewBoxStr,\n style: svgStyle,\n },\n paths,\n sectionsPadding,\n }\n}\n\n/**\n * Measure wrapper and section elements to get width and section bottom Ys.\n * Children with the excludeClassName (default: same class used on the SVG by serpentineBorder) are excluded.\n * horizontalOverflow is resolved to pixels using strokeCount and strokeWidth.\n *\n * @param {HTMLElement} wrapperEl\n * @param {{\n * layoutMode: 'content' | 'border'\n * horizontalOverflow?: number | 'borderWidth' | 'halfBorderWidth'\n * strokeCount: number\n * strokeWidth: number\n * excludeClassName?: string\n * }} options\n * @returns {{ width: number, sectionBottomYs: number[] } | null}\n */\nexport function measureSections(wrapperEl, options) {\n const { layoutMode, horizontalOverflow = 0, strokeCount, strokeWidth, excludeClassName = DEFAULT_SVG_CLASS } = options\n const BORDER_EXTRA = resolveOverflowToPixels(horizontalOverflow, strokeCount, strokeWidth)\n if (!wrapperEl) return null\n\n const sectionEls = excludeClassName\n ? Array.from(wrapperEl.children).filter((el) => !el.classList.contains(excludeClassName))\n : Array.from(wrapperEl.children)\n\n if (sectionEls.length === 0) return null\n\n const rect = wrapperEl.getBoundingClientRect()\n const baseWidth = rect.width\n\n const W =\n layoutMode === 'border'\n ? Math.max(1, baseWidth - 2 * BORDER_EXTRA)\n : Math.max(1, baseWidth)\n\n const Y = [0]\n for (let i = 0; i < sectionEls.length; i++) {\n const r = sectionEls[i].getBoundingClientRect()\n Y.push(r.top - rect.top + r.height)\n }\n\n return { width: W, sectionBottomYs: Y }\n}\n","import { useEffect, useRef, useState } from 'react'\nimport { measureSections, serpentineBorder } from './serpentineCore.js'\n\nfunction cssStringToStyleObject(css) {\n const obj = {}\n if (!css || typeof css !== 'string') return obj\n for (const decl of css.split(';')) {\n const colon = decl.indexOf(':')\n if (colon === -1) continue\n const key = decl.slice(0, colon).trim().replace(/-([a-z])/g, (_, c) => c.toUpperCase())\n const value = decl.slice(colon + 1).trim()\n if (key) obj[key] = value\n }\n return obj\n}\n\nfunction pathAttrsForReact(attrs) {\n return Object.fromEntries(\n Object.entries(attrs).map(([k, v]) => [k === 'stroke-width' ? 'strokeWidth' : k, v])\n )\n}\n\nfunction SerpentineBorder({\n children,\n strokeCount,\n strokeWidth,\n radius,\n horizontalOverflow,\n colors,\n layoutMode,\n}) {\n const wrapperRef = useRef(null)\n const [borderData, setBorderData] = useState(null)\n\n useEffect(() => {\n const wrapper = wrapperRef.current\n if (!wrapper) return\n\n const measure = () => {\n const measured = measureSections(wrapper, {\n layoutMode,\n horizontalOverflow,\n strokeCount,\n strokeWidth,\n })\n if (!measured) return\n\n const data = serpentineBorder({\n width: measured.width,\n sectionBottomYs: measured.sectionBottomYs,\n strokeCount,\n strokeWidth,\n radius,\n horizontalOverflow,\n colors,\n layoutMode,\n })\n setBorderData(data)\n }\n\n measure()\n const ro = new ResizeObserver(measure)\n ro.observe(wrapper)\n return () => ro.disconnect()\n }, [strokeCount, strokeWidth, radius, horizontalOverflow, colors, layoutMode])\n\n return (\n <div\n ref={wrapperRef}\n className=\"serpentine-wrapper\"\n style={borderData?.wrapperStyle ?? { position: 'relative', boxSizing: 'border-box' }}\n data-testid=\"serpentine-wrapper\"\n >\n {borderData && (() => {\n const { class: className, style: styleStr, ...restSvgAttrs } = borderData.svgAttributes\n const style = typeof styleStr === 'string' ? cssStringToStyleObject(styleStr) : styleStr\n return (\n <svg\n data-testid=\"serpentine-svg\"\n className={className}\n style={style}\n {...restSvgAttrs}\n >\n {borderData.paths.map((pathAttributes, i) => (\n <path key={i} {...pathAttrsForReact(pathAttributes)} />\n ))}\n </svg>\n )\n })()}\n {children}\n </div>\n )\n}\n\nexport default SerpentineBorder\n"],"names":["DEFAULT_COLORS","resolveOverflowToPixels","horizontalOverflow","N","STROKE_WIDTH","totalBorderWidth","styleObjectToCss","obj","k","v","key","val","buildPathD","W","Y","R","COLORS","TOP_ARC_SHIFT","Y_OFFSET","O_TOTAL","BORDER_EXTRA","R1","RIGHT_EXTEND","n","parts","i","o","oj","r","rj","rj1","leftOffset","xLeft","rightExt","xRight","xLeftArc","xRightArc","xRightR1","yCurrTop","segs","t","yCurr","yNext","yExit","lastY","DEFAULT_SVG_CLASS","DEFAULTS","serpentineBorder","options","layoutMode","svgClassName","wrapperEl","measured","measureSections","TOTAL_BORDER_WIDTH","TOP_OFFSET","wrapperStyle","svgStyleObj","svgStyle","paths","totalHeight","totalWidth","viewBoxHeight","viewBoxMinX","viewBoxMinY","viewBoxStr","halfBorderWidth","insetSide","sectionsPadding","top","bottom","left","right","strokeCount","strokeWidth","excludeClassName","sectionEls","el","rect","baseWidth","cssStringToStyleObject","css","decl","colon","_","c","value","pathAttrsForReact","attrs","SerpentineBorder","children","radius","colors","wrapperRef","useRef","borderData","setBorderData","useState","useEffect","wrapper","measure","data","ro","jsxs","className","styleStr","restSvgAttrs","style","jsx","pathAttributes"],"mappings":"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"}
1
+ {"version":3,"file":"serpentine-border.cjs","sources":["../src/constants.js","../src/serpentineCore.js","../src/SerpentineBorder.jsx"],"sourcesContent":["export const DEFAULT_COLORS = ['#ffffff', '#000000']\n","/**\n * Vanilla JS core for serpentine border SVG generation.\n * Single export: call with measured dimensions and options to get everything needed to render.\n */\n\nimport { DEFAULT_COLORS } from './constants.js'\n\nfunction resolveOverflowToPixels(horizontalOverflow, N, STROKE_WIDTH) {\n if (typeof horizontalOverflow === 'number') return horizontalOverflow\n const totalBorderWidth = N * STROKE_WIDTH\n if (horizontalOverflow === 'borderWidth') return totalBorderWidth\n if (horizontalOverflow === 'halfBorderWidth') return totalBorderWidth / 2\n return 0\n}\n\nfunction styleObjectToCss(obj) {\n return Object.entries(obj)\n .map(([k, v]) => {\n const key = k.replace(/([A-Z])/g, '-$1').toLowerCase()\n const val = typeof v === 'number' && !Number.isNaN(v) ? `${v}px` : String(v)\n return `${key}: ${val}`\n })\n .join('; ')\n}\n\nfunction buildPathD(W, Y, N, R, STROKE_WIDTH, COLORS, TOP_ARC_SHIFT, Y_OFFSET, O_TOTAL, BORDER_EXTRA) {\n const R1 = STROKE_WIDTH * (N - 1)\n const RIGHT_EXTEND = STROKE_WIDTH / 2\n const n = Y.length - 1\n const parts = []\n for (let i = 0; i < N; i++) {\n const o = i * STROKE_WIDTH\n const j = N - 1 - i\n const oj = j * STROKE_WIDTH\n const r = R - o\n const rj = R - oj\n const r1 = R1 - o\n const rj1 = R1 - oj\n\n const leftOffset = O_TOTAL - BORDER_EXTRA + RIGHT_EXTEND\n const xLeft = o - O_TOTAL + leftOffset\n const rightExt = BORDER_EXTRA\n const xRight = W + rightExt - oj - RIGHT_EXTEND\n const xLeftArc = xLeft + (R - o)\n const xRightArc = W + rightExt - R - RIGHT_EXTEND\n const xRightR1 = W + rightExt - R1 - RIGHT_EXTEND\n\n const yCurrTop = O_TOTAL + Y_OFFSET\n const segs = [\n `M ${xRight} ${yCurrTop - R1 - STROKE_WIDTH / 2 - TOP_ARC_SHIFT}`,\n `L ${xRight} ${yCurrTop - R1 - TOP_ARC_SHIFT}`,\n `A ${rj1} ${rj1} 0 0 1 ${xRightR1} ${yCurrTop - oj - TOP_ARC_SHIFT}`,\n `L ${xLeftArc} ${o + Y_OFFSET - TOP_ARC_SHIFT}`,\n `A ${r} ${r} 0 0 0 ${xLeft} ${R + Y_OFFSET - TOP_ARC_SHIFT}`,\n `L ${xLeft} ${R + Y_OFFSET}`,\n ]\n\n for (let t = 0; t < n - 1; t++) {\n const yCurr = Y[t + 1]\n const yNext = Y[t + 2]\n const yExit = yCurr + R - O_TOTAL + Y_OFFSET\n\n if (t % 2 === 0) {\n segs.push(`L ${xLeft} ${yCurr - R + Y_OFFSET}`)\n segs.push(`A ${r} ${r} 0 0 0 ${xLeftArc} ${yCurr - o + Y_OFFSET}`)\n segs.push(`L ${xRightArc} ${yCurr - o + Y_OFFSET}`)\n segs.push(`A ${rj} ${rj} 0 0 1 ${xRight} ${yExit}`)\n segs.push(`L ${xRight} ${yNext - R + Y_OFFSET}`)\n } else {\n segs.push(`L ${xRight} ${yCurr - R + Y_OFFSET}`)\n segs.push(`A ${rj} ${rj} 0 0 1 ${xRightArc} ${yCurr - oj + Y_OFFSET}`)\n segs.push(`L ${xLeftArc} ${yCurr - oj + Y_OFFSET}`)\n segs.push(`A ${r} ${r} 0 0 0 ${xLeft} ${yExit}`)\n segs.push(`L ${xLeft} ${yNext - R + Y_OFFSET}`)\n }\n }\n\n const lastY = Y[n]\n if ((n - 2) % 2 === 0) {\n segs.push(`L ${xRight} ${lastY}`)\n } else {\n segs.push(`L ${xLeft} ${lastY}`)\n }\n parts.push({\n d: segs.join(' '),\n stroke: COLORS[i % COLORS.length],\n 'stroke-width': STROKE_WIDTH,\n fill: 'none',\n })\n }\n return parts\n}\n\nconst DEFAULT_SVG_CLASS = 'serpentine-border-svg'\n\nconst DEFAULTS = {\n strokeCount: 5,\n strokeWidth: 8,\n radius: 50,\n horizontalOverflow: 0,\n layoutMode: 'border',\n}\n\n/**\n * Compute everything needed to render the serpentine border.\n * Accepts either (width + sectionBottomYs) for pure/custom use, or wrapperEl to measure from the DOM.\n * When using wrapperEl, returns null in non-DOM environments (e.g. SSR) or when measurement fails.\n *\n * @param {{\n * width?: number\n * sectionBottomYs?: number[]\n * wrapperEl?: HTMLElement\n * strokeCount?: number\n * strokeWidth?: number\n * radius?: number\n * horizontalOverflow?: number | 'borderWidth' | 'halfBorderWidth'\n * colors?: string[]\n * layoutMode?: 'content' | 'border'\n * svgClassName?: string\n * }} options\n * @returns {{\n * wrapperStyle: Record<string, unknown>\n * svgAttributes: { class?: string, viewBox: string, style: string }\n * paths: Array<{ d: string, stroke: string, 'stroke-width': number, fill: string }>\n * } | null}\n */\nexport function serpentineBorder(options) {\n const N = options.strokeCount ?? DEFAULTS.strokeCount\n const STROKE_WIDTH = options.strokeWidth ?? DEFAULTS.strokeWidth\n const R = options.radius ?? DEFAULTS.radius\n const horizontalOverflow = options.horizontalOverflow ?? DEFAULTS.horizontalOverflow\n const COLORS = options.colors ?? DEFAULT_COLORS\n const layoutMode = options.layoutMode ?? DEFAULTS.layoutMode\n const svgClassName = options.svgClassName ?? DEFAULT_SVG_CLASS\n\n let W, Y\n if (options.wrapperEl != null) {\n const wrapperEl = options.wrapperEl\n const hasDOM = typeof document !== 'undefined' && typeof wrapperEl.getBoundingClientRect === 'function'\n if (!hasDOM) return null\n const measured = measureSections(wrapperEl, {\n layoutMode,\n horizontalOverflow,\n strokeCount: N,\n strokeWidth: STROKE_WIDTH,\n excludeClassName: svgClassName,\n })\n if (!measured) return null\n W = measured.width\n Y = measured.sectionBottomYs\n } else {\n if (options.width == null || options.sectionBottomYs == null) return null\n W = options.width\n Y = options.sectionBottomYs\n }\n\n const BORDER_EXTRA = resolveOverflowToPixels(horizontalOverflow, N, STROKE_WIDTH)\n const O_TOTAL = (N - 1) * STROKE_WIDTH\n const TOTAL_BORDER_WIDTH = N * STROKE_WIDTH\n const TOP_OFFSET = 2 * STROKE_WIDTH\n const Y_OFFSET = O_TOTAL / 2\n const TOP_ARC_SHIFT = ((N - 1) / 2) * STROKE_WIDTH + Y_OFFSET\n\n const wrapperStyle =\n layoutMode === 'border'\n ? {\n boxSizing: 'border-box',\n position: 'relative',\n marginTop: `${TOTAL_BORDER_WIDTH / 2}px`,\n ...(BORDER_EXTRA > 0 && {\n paddingLeft: `${BORDER_EXTRA}px`,\n paddingRight: `${BORDER_EXTRA}px`,\n }),\n }\n : {\n position: 'relative',\n boxSizing: 'border-box',\n }\n\n const svgStyleObj =\n layoutMode === 'border'\n ? {\n position: 'absolute',\n overflow: 'hidden',\n ...(BORDER_EXTRA > 0\n ? { width: '100%', left: 0 }\n : { width: `calc(100% + ${2 * BORDER_EXTRA}px)`, left: -BORDER_EXTRA }),\n top: -(TOP_OFFSET + TOP_ARC_SHIFT),\n height: `calc(100% + ${TOP_OFFSET + TOP_ARC_SHIFT}px)`,\n }\n : {\n position: 'absolute',\n overflow: 'hidden',\n width: `calc(100% + ${2 * BORDER_EXTRA}px)`,\n left: -BORDER_EXTRA,\n top: -(TOP_OFFSET + TOP_ARC_SHIFT),\n height: `calc(100% + ${TOP_OFFSET + TOP_ARC_SHIFT}px)`,\n }\n const svgStyle = styleObjectToCss(svgStyleObj)\n\n const paths = buildPathD(W, Y, N, R, STROKE_WIDTH, COLORS, TOP_ARC_SHIFT, Y_OFFSET, O_TOTAL, BORDER_EXTRA)\n\n const totalHeight = Y[Y.length - 1] ?? 0\n const totalWidth = Math.max(1, W + 2 * BORDER_EXTRA)\n const viewBoxHeight = totalHeight + TOP_OFFSET + TOP_ARC_SHIFT\n const viewBoxMinX = BORDER_EXTRA > 0 ? -BORDER_EXTRA : 0\n const viewBoxMinY = -STROKE_WIDTH * 2 - TOP_ARC_SHIFT\n const viewBoxStr = `${viewBoxMinX} ${viewBoxMinY} ${totalWidth} ${viewBoxHeight}`\n\n return {\n wrapperStyle,\n svgAttributes: {\n class: svgClassName,\n viewBox: viewBoxStr,\n style: svgStyle,\n },\n paths,\n }\n}\n\n/**\n * Compute padding for each section so content does not overlap the border.\n * Returns an object with even, odd, and last: use for even-indexed sections, odd-indexed sections, and the last section respectively.\n *\n * @param {{\n * sectionCount: number\n * strokeCount: number\n * strokeWidth: number\n * horizontalOverflow?: number | 'borderWidth' | 'halfBorderWidth'\n * }} options\n * @returns {{ even: { top: number, right: number, bottom: number, left: number }, odd: { top: number, right: number, bottom: number, left: number }, last: { top: number, right: number, bottom: number, left: number } }}\n */\nexport function getSectionsPadding(options) {\n const { sectionCount: n, strokeCount: N, strokeWidth: STROKE_WIDTH } = options\n const horizontalOverflow = options.horizontalOverflow ?? 0\n const TOTAL_BORDER_WIDTH = N * STROKE_WIDTH\n const BORDER_EXTRA = resolveOverflowToPixels(horizontalOverflow, N, STROKE_WIDTH)\n const halfBorderWidth = TOTAL_BORDER_WIDTH / 2\n const insetSide = TOTAL_BORDER_WIDTH - BORDER_EXTRA\n const even = { top: halfBorderWidth, right: 0, bottom: halfBorderWidth, left: insetSide }\n const odd = { top: halfBorderWidth, right: insetSide, bottom: halfBorderWidth, left: 0 }\n const lastIsEven = n > 0 && (n - 1) % 2 === 0\n const last = {\n top: halfBorderWidth,\n right: lastIsEven ? 0 : insetSide,\n bottom: 0,\n left: lastIsEven ? insetSide : 0,\n }\n return { even, odd, last }\n}\n\n/**\n * Measure wrapper and section elements to get width and section bottom Ys.\n * Children with the excludeClassName (default: same class used on the SVG by serpentineBorder) are excluded.\n * horizontalOverflow is resolved to pixels using strokeCount and strokeWidth.\n *\n * @param {HTMLElement} wrapperEl\n * @param {{\n * layoutMode: 'content' | 'border'\n * horizontalOverflow?: number | 'borderWidth' | 'halfBorderWidth'\n * strokeCount: number\n * strokeWidth: number\n * excludeClassName?: string\n * }} options\n * @returns {{ width: number, sectionBottomYs: number[] } | null}\n */\nexport function measureSections(wrapperEl, options) {\n const { layoutMode, horizontalOverflow = 0, strokeCount, strokeWidth, excludeClassName = DEFAULT_SVG_CLASS } = options\n const BORDER_EXTRA = resolveOverflowToPixels(horizontalOverflow, strokeCount, strokeWidth)\n if (!wrapperEl) return null\n\n const sectionEls = excludeClassName\n ? Array.from(wrapperEl.children).filter((el) => !el.classList.contains(excludeClassName))\n : Array.from(wrapperEl.children)\n\n if (sectionEls.length === 0) return null\n\n const rect = wrapperEl.getBoundingClientRect()\n const baseWidth = rect.width\n\n const W =\n layoutMode === 'border'\n ? Math.max(1, baseWidth - 2 * BORDER_EXTRA)\n : Math.max(1, baseWidth)\n\n const Y = [0]\n for (let i = 0; i < sectionEls.length; i++) {\n const r = sectionEls[i].getBoundingClientRect()\n Y.push(r.top - rect.top + r.height)\n }\n\n return { width: W, sectionBottomYs: Y }\n}\n","import { useEffect, useRef, useState } from 'react'\nimport { measureSections, serpentineBorder } from './serpentineCore.js'\n\nfunction cssStringToStyleObject(css) {\n const obj = {}\n if (!css || typeof css !== 'string') return obj\n for (const decl of css.split(';')) {\n const colon = decl.indexOf(':')\n if (colon === -1) continue\n const key = decl.slice(0, colon).trim().replace(/-([a-z])/g, (_, c) => c.toUpperCase())\n const value = decl.slice(colon + 1).trim()\n if (key) obj[key] = value\n }\n return obj\n}\n\nfunction pathAttrsForReact(attrs) {\n return Object.fromEntries(\n Object.entries(attrs).map(([k, v]) => [k === 'stroke-width' ? 'strokeWidth' : k, v])\n )\n}\n\nfunction SerpentineBorder({\n children,\n strokeCount,\n strokeWidth,\n radius,\n horizontalOverflow,\n colors,\n layoutMode,\n}) {\n const wrapperRef = useRef(null)\n const [borderData, setBorderData] = useState(null)\n\n useEffect(() => {\n const wrapper = wrapperRef.current\n if (!wrapper) return\n\n const measure = () => {\n const measured = measureSections(wrapper, {\n layoutMode,\n horizontalOverflow,\n strokeCount,\n strokeWidth,\n })\n if (!measured) return\n\n const data = serpentineBorder({\n width: measured.width,\n sectionBottomYs: measured.sectionBottomYs,\n strokeCount,\n strokeWidth,\n radius,\n horizontalOverflow,\n colors,\n layoutMode,\n })\n setBorderData(data)\n }\n\n measure()\n const ro = new ResizeObserver(measure)\n ro.observe(wrapper)\n return () => ro.disconnect()\n }, [strokeCount, strokeWidth, radius, horizontalOverflow, colors, layoutMode])\n\n return (\n <div\n ref={wrapperRef}\n className=\"serpentine-wrapper\"\n style={borderData?.wrapperStyle ?? { position: 'relative', boxSizing: 'border-box' }}\n data-testid=\"serpentine-wrapper\"\n >\n {borderData && (() => {\n const { class: className, style: styleStr, ...restSvgAttrs } = borderData.svgAttributes\n const style = typeof styleStr === 'string' ? cssStringToStyleObject(styleStr) : styleStr\n return (\n <svg\n data-testid=\"serpentine-svg\"\n className={className}\n style={style}\n {...restSvgAttrs}\n >\n {borderData.paths.map((pathAttributes, i) => (\n <path key={i} {...pathAttrsForReact(pathAttributes)} />\n ))}\n </svg>\n )\n })()}\n {children}\n </div>\n )\n}\n\nexport default SerpentineBorder\n"],"names":["DEFAULT_COLORS","resolveOverflowToPixels","horizontalOverflow","N","STROKE_WIDTH","totalBorderWidth","styleObjectToCss","obj","k","v","key","val","buildPathD","W","Y","R","COLORS","TOP_ARC_SHIFT","Y_OFFSET","O_TOTAL","BORDER_EXTRA","R1","RIGHT_EXTEND","n","parts","i","o","oj","r","rj","rj1","leftOffset","xLeft","rightExt","xRight","xLeftArc","xRightArc","xRightR1","yCurrTop","segs","t","yCurr","yNext","yExit","lastY","DEFAULT_SVG_CLASS","DEFAULTS","serpentineBorder","options","layoutMode","svgClassName","wrapperEl","measured","measureSections","TOTAL_BORDER_WIDTH","TOP_OFFSET","wrapperStyle","svgStyleObj","svgStyle","paths","totalHeight","totalWidth","viewBoxHeight","viewBoxMinX","viewBoxMinY","viewBoxStr","getSectionsPadding","halfBorderWidth","insetSide","even","odd","lastIsEven","strokeCount","strokeWidth","excludeClassName","sectionEls","el","rect","baseWidth","cssStringToStyleObject","css","decl","colon","_","c","value","pathAttrsForReact","attrs","SerpentineBorder","children","radius","colors","wrapperRef","useRef","borderData","setBorderData","useState","useEffect","wrapper","measure","data","ro","jsxs","className","styleStr","restSvgAttrs","style","jsx","pathAttributes"],"mappings":"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"}
@@ -1,191 +1,194 @@
1
- import { jsxs as V, jsx as U } from "react/jsx-runtime";
2
- import { useRef as Z, useState as q, useEffect as J } from "react";
3
- const Q = ["#ffffff", "#000000"];
4
- function X(t, s, e) {
1
+ import { jsxs as G, jsx as U } from "react/jsx-runtime";
2
+ import { useRef as P, useState as V, useEffect as Z } from "react";
3
+ const q = ["#ffffff", "#000000"];
4
+ function D(t, o, e) {
5
5
  if (typeof t == "number") return t;
6
- const n = s * e;
6
+ const n = o * e;
7
7
  return t === "borderWidth" ? n : t === "halfBorderWidth" ? n / 2 : 0;
8
8
  }
9
- function E(t) {
10
- return Object.entries(t).map(([s, e]) => {
11
- const n = s.replace(/([A-Z])/g, "-$1").toLowerCase(), c = typeof e == "number" && !Number.isNaN(e) ? `${e}px` : String(e);
12
- return `${n}: ${c}`;
9
+ function J(t) {
10
+ return Object.entries(t).map(([o, e]) => {
11
+ const n = o.replace(/([A-Z])/g, "-$1").toLowerCase(), i = typeof e == "number" && !Number.isNaN(e) ? `${e}px` : String(e);
12
+ return `${n}: ${i}`;
13
13
  }).join("; ");
14
14
  }
15
- function H(t, s, e, n, c, m, u, r, i, $) {
16
- const o = c * (e - 1), a = c / 2, f = s.length - 1, l = [];
17
- for (let p = 0; p < e; p++) {
18
- const d = p * c, y = (e - 1 - p) * c, w = n - d, L = n - y, N = o - y, S = i - $ + a, b = d - i + S, A = $, v = t + A - y - a, C = b + (n - d), k = t + A - n - a, W = t + A - o - a, j = i + r, h = [
19
- `M ${v} ${j - o - c / 2 - u}`,
20
- `L ${v} ${j - o - u}`,
21
- `A ${N} ${N} 0 0 1 ${W} ${j - y - u}`,
22
- `L ${C} ${d + r - u}`,
23
- `A ${w} ${w} 0 0 0 ${b} ${n + r - u}`,
24
- `L ${b} ${n + r}`
15
+ function Q(t, o, e, n, i, p, u, s, c, h) {
16
+ const r = i * (e - 1), a = i / 2, $ = o.length - 1, l = [];
17
+ for (let f = 0; f < e; f++) {
18
+ const d = f * i, x = (e - 1 - f) * i, b = n - d, B = n - x, M = r - x, N = c - h + a, m = d - c + N, L = h, w = t + L - x - a, A = m + (n - d), j = t + L - n - a, W = t + L - r - a, v = c + s, g = [
19
+ `M ${w} ${v - r - i / 2 - u}`,
20
+ `L ${w} ${v - r - u}`,
21
+ `A ${M} ${M} 0 0 1 ${W} ${v - x - u}`,
22
+ `L ${A} ${d + s - u}`,
23
+ `A ${b} ${b} 0 0 0 ${m} ${n + s - u}`,
24
+ `L ${m} ${n + s}`
25
25
  ];
26
- for (let B = 0; B < f - 1; B++) {
27
- const g = s[B + 1], D = s[B + 2], z = g + n - i + r;
28
- B % 2 === 0 ? (h.push(`L ${b} ${g - n + r}`), h.push(`A ${w} ${w} 0 0 0 ${C} ${g - d + r}`), h.push(`L ${k} ${g - d + r}`), h.push(`A ${L} ${L} 0 0 1 ${v} ${z}`), h.push(`L ${v} ${D - n + r}`)) : (h.push(`L ${v} ${g - n + r}`), h.push(`A ${L} ${L} 0 0 1 ${k} ${g - y + r}`), h.push(`L ${C} ${g - y + r}`), h.push(`A ${w} ${w} 0 0 0 ${b} ${z}`), h.push(`L ${b} ${D - n + r}`));
26
+ for (let C = 0; C < $ - 1; C++) {
27
+ const y = o[C + 1], S = o[C + 2], E = y + n - c + s;
28
+ C % 2 === 0 ? (g.push(`L ${m} ${y - n + s}`), g.push(`A ${b} ${b} 0 0 0 ${A} ${y - d + s}`), g.push(`L ${j} ${y - d + s}`), g.push(`A ${B} ${B} 0 0 1 ${w} ${E}`), g.push(`L ${w} ${S - n + s}`)) : (g.push(`L ${w} ${y - n + s}`), g.push(`A ${B} ${B} 0 0 1 ${j} ${y - x + s}`), g.push(`L ${A} ${y - x + s}`), g.push(`A ${b} ${b} 0 0 0 ${m} ${E}`), g.push(`L ${m} ${S - n + s}`));
29
29
  }
30
- const x = s[f];
31
- (f - 2) % 2 === 0 ? h.push(`L ${v} ${x}`) : h.push(`L ${b} ${x}`), l.push({
32
- d: h.join(" "),
33
- stroke: m[p % m.length],
34
- "stroke-width": c,
30
+ const z = o[$];
31
+ ($ - 2) % 2 === 0 ? g.push(`L ${w} ${z}`) : g.push(`L ${m} ${z}`), l.push({
32
+ d: g.join(" "),
33
+ stroke: p[f % p.length],
34
+ "stroke-width": i,
35
35
  fill: "none"
36
36
  });
37
37
  }
38
38
  return l;
39
39
  }
40
- const G = "serpentine-border-svg", M = {
40
+ const X = "serpentine-border-svg", k = {
41
41
  strokeCount: 5,
42
42
  strokeWidth: 8,
43
43
  radius: 50,
44
44
  horizontalOverflow: 0,
45
45
  layoutMode: "border"
46
46
  };
47
- function O(t) {
48
- const s = t.strokeCount ?? M.strokeCount, e = t.strokeWidth ?? M.strokeWidth, n = t.radius ?? M.radius, c = t.horizontalOverflow ?? M.horizontalOverflow, m = t.colors ?? Q, u = t.layoutMode ?? M.layoutMode, r = t.svgClassName ?? G;
49
- let i, $;
47
+ function H(t) {
48
+ const o = t.strokeCount ?? k.strokeCount, e = t.strokeWidth ?? k.strokeWidth, n = t.radius ?? k.radius, i = t.horizontalOverflow ?? k.horizontalOverflow, p = t.colors ?? q, u = t.layoutMode ?? k.layoutMode, s = t.svgClassName ?? X;
49
+ let c, h;
50
50
  if (t.wrapperEl != null) {
51
- const x = t.wrapperEl;
52
- if (!(typeof document < "u" && typeof x.getBoundingClientRect == "function")) return null;
53
- const g = P(x, {
51
+ const j = t.wrapperEl;
52
+ if (!(typeof document < "u" && typeof j.getBoundingClientRect == "function")) return null;
53
+ const v = O(j, {
54
54
  layoutMode: u,
55
- horizontalOverflow: c,
56
- strokeCount: s,
55
+ horizontalOverflow: i,
56
+ strokeCount: o,
57
57
  strokeWidth: e,
58
- excludeClassName: r
58
+ excludeClassName: s
59
59
  });
60
- if (!g) return null;
61
- i = g.width, $ = g.sectionBottomYs;
60
+ if (!v) return null;
61
+ c = v.width, h = v.sectionBottomYs;
62
62
  } else {
63
63
  if (t.width == null || t.sectionBottomYs == null) return null;
64
- i = t.width, $ = t.sectionBottomYs;
64
+ c = t.width, h = t.sectionBottomYs;
65
65
  }
66
- const o = X(c, s, e), a = (s - 1) * e, f = s * e, l = 2 * e, p = a / 2, d = (s - 1) / 2 * e + p, R = u === "border" ? {
66
+ const r = D(i, o, e), a = (o - 1) * e, $ = o * e, l = 2 * e, f = a / 2, d = (o - 1) / 2 * e + f, R = u === "border" ? {
67
67
  boxSizing: "border-box",
68
68
  position: "relative",
69
- marginTop: `${f / 2}px`,
70
- ...o > 0 && {
71
- paddingLeft: `${o}px`,
72
- paddingRight: `${o}px`
69
+ marginTop: `${$ / 2}px`,
70
+ ...r > 0 && {
71
+ paddingLeft: `${r}px`,
72
+ paddingRight: `${r}px`
73
73
  }
74
74
  } : {
75
75
  position: "relative",
76
76
  boxSizing: "border-box"
77
- }, y = u === "border" ? {
77
+ }, x = u === "border" ? {
78
78
  position: "absolute",
79
79
  overflow: "hidden",
80
- width: "100%",
81
- left: 0,
80
+ ...r > 0 ? { width: "100%", left: 0 } : { width: `calc(100% + ${2 * r}px)`, left: -r },
82
81
  top: -(l + d),
83
82
  height: `calc(100% + ${l + d}px)`
84
83
  } : {
85
84
  position: "absolute",
86
85
  overflow: "hidden",
87
- width: `calc(100% + ${2 * o}px)`,
88
- left: -o,
86
+ width: `calc(100% + ${2 * r}px)`,
87
+ left: -r,
89
88
  top: -(l + d),
90
89
  height: `calc(100% + ${l + d}px)`
91
- }, w = E(y), L = H(i, $, s, n, e, m, d, p, a, o), N = $[$.length - 1] ?? 0, S = Math.max(1, i + 2 * o), b = N + l + d, A = o > 0 ? -o : 0, v = -e * 2 - d, C = `${A} ${v} ${S} ${b}`, k = $.length - 1, W = f / 2, j = f - o, h = [];
92
- for (let x = 0; x < k; x++) {
93
- const B = W, g = x === k - 1 ? 0 : W, D = x % 2 === 0 ? j : 0, z = x % 2 === 0 ? 0 : j;
94
- h.push({ top: B, right: z, bottom: g, left: D });
95
- }
90
+ }, b = J(x), B = Q(c, h, o, n, e, p, d, f, a, r), M = h[h.length - 1] ?? 0, N = Math.max(1, c + 2 * r), m = M + l + d, L = r > 0 ? -r : 0, w = -e * 2 - d, A = `${L} ${w} ${N} ${m}`;
96
91
  return {
97
92
  wrapperStyle: R,
98
93
  svgAttributes: {
99
- class: r,
100
- viewBox: C,
101
- style: w
94
+ class: s,
95
+ viewBox: A,
96
+ style: b
102
97
  },
103
- paths: L,
104
- sectionsPadding: h
98
+ paths: B
105
99
  };
106
100
  }
107
- function P(t, s) {
108
- const { layoutMode: e, horizontalOverflow: n = 0, strokeCount: c, strokeWidth: m, excludeClassName: u = G } = s, r = X(n, c, m);
101
+ function _(t) {
102
+ const { sectionCount: o, strokeCount: e, strokeWidth: n } = t, i = t.horizontalOverflow ?? 0, p = e * n, u = D(i, e, n), s = p / 2, c = p - u, h = { top: s, right: 0, bottom: s, left: c }, r = { top: s, right: c, bottom: s, left: 0 }, a = o > 0 && (o - 1) % 2 === 0;
103
+ return { even: h, odd: r, last: {
104
+ top: s,
105
+ right: a ? 0 : c,
106
+ bottom: 0,
107
+ left: a ? c : 0
108
+ } };
109
+ }
110
+ function O(t, o) {
111
+ const { layoutMode: e, horizontalOverflow: n = 0, strokeCount: i, strokeWidth: p, excludeClassName: u = X } = o, s = D(n, i, p);
109
112
  if (!t) return null;
110
- const i = u ? Array.from(t.children).filter((l) => !l.classList.contains(u)) : Array.from(t.children);
111
- if (i.length === 0) return null;
112
- const $ = t.getBoundingClientRect(), o = $.width, a = e === "border" ? Math.max(1, o - 2 * r) : Math.max(1, o), f = [0];
113
- for (let l = 0; l < i.length; l++) {
114
- const p = i[l].getBoundingClientRect();
115
- f.push(p.top - $.top + p.height);
113
+ const c = u ? Array.from(t.children).filter((l) => !l.classList.contains(u)) : Array.from(t.children);
114
+ if (c.length === 0) return null;
115
+ const h = t.getBoundingClientRect(), r = h.width, a = e === "border" ? Math.max(1, r - 2 * s) : Math.max(1, r), $ = [0];
116
+ for (let l = 0; l < c.length; l++) {
117
+ const f = c[l].getBoundingClientRect();
118
+ $.push(f.top - h.top + f.height);
116
119
  }
117
- return { width: a, sectionBottomYs: f };
120
+ return { width: a, sectionBottomYs: $ };
118
121
  }
119
- function K(t) {
120
- const s = {};
121
- if (!t || typeof t != "string") return s;
122
+ function I(t) {
123
+ const o = {};
124
+ if (!t || typeof t != "string") return o;
122
125
  for (const e of t.split(";")) {
123
126
  const n = e.indexOf(":");
124
127
  if (n === -1) continue;
125
- const c = e.slice(0, n).trim().replace(/-([a-z])/g, (u, r) => r.toUpperCase()), m = e.slice(n + 1).trim();
126
- c && (s[c] = m);
128
+ const i = e.slice(0, n).trim().replace(/-([a-z])/g, (u, s) => s.toUpperCase()), p = e.slice(n + 1).trim();
129
+ i && (o[i] = p);
127
130
  }
128
- return s;
131
+ return o;
129
132
  }
130
- function Y(t) {
133
+ function K(t) {
131
134
  return Object.fromEntries(
132
- Object.entries(t).map(([s, e]) => [s === "stroke-width" ? "strokeWidth" : s, e])
135
+ Object.entries(t).map(([o, e]) => [o === "stroke-width" ? "strokeWidth" : o, e])
133
136
  );
134
137
  }
135
- function _({
138
+ function F({
136
139
  children: t,
137
- strokeCount: s,
140
+ strokeCount: o,
138
141
  strokeWidth: e,
139
142
  radius: n,
140
- horizontalOverflow: c,
141
- colors: m,
143
+ horizontalOverflow: i,
144
+ colors: p,
142
145
  layoutMode: u
143
146
  }) {
144
- const r = Z(null), [i, $] = q(null);
145
- return J(() => {
146
- const o = r.current;
147
- if (!o) return;
147
+ const s = P(null), [c, h] = V(null);
148
+ return Z(() => {
149
+ const r = s.current;
150
+ if (!r) return;
148
151
  const a = () => {
149
- const l = P(o, {
152
+ const l = O(r, {
150
153
  layoutMode: u,
151
- horizontalOverflow: c,
152
- strokeCount: s,
154
+ horizontalOverflow: i,
155
+ strokeCount: o,
153
156
  strokeWidth: e
154
157
  });
155
158
  if (!l) return;
156
- const p = O({
159
+ const f = H({
157
160
  width: l.width,
158
161
  sectionBottomYs: l.sectionBottomYs,
159
- strokeCount: s,
162
+ strokeCount: o,
160
163
  strokeWidth: e,
161
164
  radius: n,
162
- horizontalOverflow: c,
163
- colors: m,
165
+ horizontalOverflow: i,
166
+ colors: p,
164
167
  layoutMode: u
165
168
  });
166
- $(p);
169
+ h(f);
167
170
  };
168
171
  a();
169
- const f = new ResizeObserver(a);
170
- return f.observe(o), () => f.disconnect();
171
- }, [s, e, n, c, m, u]), /* @__PURE__ */ V(
172
+ const $ = new ResizeObserver(a);
173
+ return $.observe(r), () => $.disconnect();
174
+ }, [o, e, n, i, p, u]), /* @__PURE__ */ G(
172
175
  "div",
173
176
  {
174
- ref: r,
177
+ ref: s,
175
178
  className: "serpentine-wrapper",
176
- style: (i == null ? void 0 : i.wrapperStyle) ?? { position: "relative", boxSizing: "border-box" },
179
+ style: (c == null ? void 0 : c.wrapperStyle) ?? { position: "relative", boxSizing: "border-box" },
177
180
  "data-testid": "serpentine-wrapper",
178
181
  children: [
179
- i && (() => {
180
- const { class: o, style: a, ...f } = i.svgAttributes, l = typeof a == "string" ? K(a) : a;
182
+ c && (() => {
183
+ const { class: r, style: a, ...$ } = c.svgAttributes, l = typeof a == "string" ? I(a) : a;
181
184
  return /* @__PURE__ */ U(
182
185
  "svg",
183
186
  {
184
187
  "data-testid": "serpentine-svg",
185
- className: o,
188
+ className: r,
186
189
  style: l,
187
- ...f,
188
- children: i.paths.map((p, d) => /* @__PURE__ */ U("path", { ...Y(p) }, d))
190
+ ...$,
191
+ children: c.paths.map((f, d) => /* @__PURE__ */ U("path", { ...K(f) }, d))
189
192
  }
190
193
  );
191
194
  })(),
@@ -195,7 +198,8 @@ function _({
195
198
  );
196
199
  }
197
200
  export {
198
- _ as SerpentineBorder,
199
- O as serpentineBorder
201
+ F as SerpentineBorder,
202
+ _ as getSectionsPadding,
203
+ H as serpentineBorder
200
204
  };
201
205
  //# sourceMappingURL=serpentine-border.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"serpentine-border.js","sources":["../src/constants.js","../src/serpentineCore.js","../src/SerpentineBorder.jsx"],"sourcesContent":["export const DEFAULT_COLORS = ['#ffffff', '#000000']\n","/**\n * Vanilla JS core for serpentine border SVG generation.\n * Single export: call with measured dimensions and options to get everything needed to render.\n */\n\nimport { DEFAULT_COLORS } from './constants.js'\n\nfunction resolveOverflowToPixels(horizontalOverflow, N, STROKE_WIDTH) {\n if (typeof horizontalOverflow === 'number') return horizontalOverflow\n const totalBorderWidth = N * STROKE_WIDTH\n if (horizontalOverflow === 'borderWidth') return totalBorderWidth\n if (horizontalOverflow === 'halfBorderWidth') return totalBorderWidth / 2\n return 0\n}\n\nfunction styleObjectToCss(obj) {\n return Object.entries(obj)\n .map(([k, v]) => {\n const key = k.replace(/([A-Z])/g, '-$1').toLowerCase()\n const val = typeof v === 'number' && !Number.isNaN(v) ? `${v}px` : String(v)\n return `${key}: ${val}`\n })\n .join('; ')\n}\n\nfunction buildPathD(W, Y, N, R, STROKE_WIDTH, COLORS, TOP_ARC_SHIFT, Y_OFFSET, O_TOTAL, BORDER_EXTRA) {\n const R1 = STROKE_WIDTH * (N - 1)\n const RIGHT_EXTEND = STROKE_WIDTH / 2\n const n = Y.length - 1\n const parts = []\n for (let i = 0; i < N; i++) {\n const o = i * STROKE_WIDTH\n const j = N - 1 - i\n const oj = j * STROKE_WIDTH\n const r = R - o\n const rj = R - oj\n const r1 = R1 - o\n const rj1 = R1 - oj\n\n const leftOffset = O_TOTAL - BORDER_EXTRA + RIGHT_EXTEND\n const xLeft = o - O_TOTAL + leftOffset\n const rightExt = BORDER_EXTRA\n const xRight = W + rightExt - oj - RIGHT_EXTEND\n const xLeftArc = xLeft + (R - o)\n const xRightArc = W + rightExt - R - RIGHT_EXTEND\n const xRightR1 = W + rightExt - R1 - RIGHT_EXTEND\n\n const yCurrTop = O_TOTAL + Y_OFFSET\n const segs = [\n `M ${xRight} ${yCurrTop - R1 - STROKE_WIDTH / 2 - TOP_ARC_SHIFT}`,\n `L ${xRight} ${yCurrTop - R1 - TOP_ARC_SHIFT}`,\n `A ${rj1} ${rj1} 0 0 1 ${xRightR1} ${yCurrTop - oj - TOP_ARC_SHIFT}`,\n `L ${xLeftArc} ${o + Y_OFFSET - TOP_ARC_SHIFT}`,\n `A ${r} ${r} 0 0 0 ${xLeft} ${R + Y_OFFSET - TOP_ARC_SHIFT}`,\n `L ${xLeft} ${R + Y_OFFSET}`,\n ]\n\n for (let t = 0; t < n - 1; t++) {\n const yCurr = Y[t + 1]\n const yNext = Y[t + 2]\n const yExit = yCurr + R - O_TOTAL + Y_OFFSET\n\n if (t % 2 === 0) {\n segs.push(`L ${xLeft} ${yCurr - R + Y_OFFSET}`)\n segs.push(`A ${r} ${r} 0 0 0 ${xLeftArc} ${yCurr - o + Y_OFFSET}`)\n segs.push(`L ${xRightArc} ${yCurr - o + Y_OFFSET}`)\n segs.push(`A ${rj} ${rj} 0 0 1 ${xRight} ${yExit}`)\n segs.push(`L ${xRight} ${yNext - R + Y_OFFSET}`)\n } else {\n segs.push(`L ${xRight} ${yCurr - R + Y_OFFSET}`)\n segs.push(`A ${rj} ${rj} 0 0 1 ${xRightArc} ${yCurr - oj + Y_OFFSET}`)\n segs.push(`L ${xLeftArc} ${yCurr - oj + Y_OFFSET}`)\n segs.push(`A ${r} ${r} 0 0 0 ${xLeft} ${yExit}`)\n segs.push(`L ${xLeft} ${yNext - R + Y_OFFSET}`)\n }\n }\n\n const lastY = Y[n]\n if ((n - 2) % 2 === 0) {\n segs.push(`L ${xRight} ${lastY}`)\n } else {\n segs.push(`L ${xLeft} ${lastY}`)\n }\n parts.push({\n d: segs.join(' '),\n stroke: COLORS[i % COLORS.length],\n 'stroke-width': STROKE_WIDTH,\n fill: 'none',\n })\n }\n return parts\n}\n\nconst DEFAULT_SVG_CLASS = 'serpentine-border-svg'\n\nconst DEFAULTS = {\n strokeCount: 5,\n strokeWidth: 8,\n radius: 50,\n horizontalOverflow: 0,\n layoutMode: 'border',\n}\n\n/**\n * Compute everything needed to render the serpentine border.\n * Accepts either (width + sectionBottomYs) for pure/custom use, or wrapperEl to measure from the DOM.\n * When using wrapperEl, returns null in non-DOM environments (e.g. SSR) or when measurement fails.\n *\n * @param {{\n * width?: number\n * sectionBottomYs?: number[]\n * wrapperEl?: HTMLElement\n * strokeCount?: number\n * strokeWidth?: number\n * radius?: number\n * horizontalOverflow?: number | 'borderWidth' | 'halfBorderWidth'\n * colors?: string[]\n * layoutMode?: 'content' | 'border'\n * svgClassName?: string\n * }} options\n * @returns {{\n * wrapperStyle: Record<string, unknown>\n * svgAttributes: { class?: string, viewBox: string, style: string }\n * paths: Array<{ d: string, stroke: string, 'stroke-width': number, fill: string }>\n * sectionsPadding: Array<{ top: number, right: number, bottom: number, left: number }>\n * } | null}\n */\nexport function serpentineBorder(options) {\n const N = options.strokeCount ?? DEFAULTS.strokeCount\n const STROKE_WIDTH = options.strokeWidth ?? DEFAULTS.strokeWidth\n const R = options.radius ?? DEFAULTS.radius\n const horizontalOverflow = options.horizontalOverflow ?? DEFAULTS.horizontalOverflow\n const COLORS = options.colors ?? DEFAULT_COLORS\n const layoutMode = options.layoutMode ?? DEFAULTS.layoutMode\n const svgClassName = options.svgClassName ?? DEFAULT_SVG_CLASS\n\n let W, Y\n if (options.wrapperEl != null) {\n const wrapperEl = options.wrapperEl\n const hasDOM = typeof document !== 'undefined' && typeof wrapperEl.getBoundingClientRect === 'function'\n if (!hasDOM) return null\n const measured = measureSections(wrapperEl, {\n layoutMode,\n horizontalOverflow,\n strokeCount: N,\n strokeWidth: STROKE_WIDTH,\n excludeClassName: svgClassName,\n })\n if (!measured) return null\n W = measured.width\n Y = measured.sectionBottomYs\n } else {\n if (options.width == null || options.sectionBottomYs == null) return null\n W = options.width\n Y = options.sectionBottomYs\n }\n\n const BORDER_EXTRA = resolveOverflowToPixels(horizontalOverflow, N, STROKE_WIDTH)\n const O_TOTAL = (N - 1) * STROKE_WIDTH\n const TOTAL_BORDER_WIDTH = N * STROKE_WIDTH\n const TOP_OFFSET = 2 * STROKE_WIDTH\n const Y_OFFSET = O_TOTAL / 2\n const TOP_ARC_SHIFT = ((N - 1) / 2) * STROKE_WIDTH + Y_OFFSET\n\n const wrapperStyle =\n layoutMode === 'border'\n ? {\n boxSizing: 'border-box',\n position: 'relative',\n marginTop: `${TOTAL_BORDER_WIDTH / 2}px`,\n ...(BORDER_EXTRA > 0 && {\n paddingLeft: `${BORDER_EXTRA}px`,\n paddingRight: `${BORDER_EXTRA}px`,\n }),\n }\n : {\n position: 'relative',\n boxSizing: 'border-box',\n }\n\n const svgStyleObj =\n layoutMode === 'border'\n ? {\n position: 'absolute',\n overflow: 'hidden',\n width: '100%',\n left: 0,\n top: -(TOP_OFFSET + TOP_ARC_SHIFT),\n height: `calc(100% + ${TOP_OFFSET + TOP_ARC_SHIFT}px)`,\n }\n : {\n position: 'absolute',\n overflow: 'hidden',\n width: `calc(100% + ${2 * BORDER_EXTRA}px)`,\n left: -BORDER_EXTRA,\n top: -(TOP_OFFSET + TOP_ARC_SHIFT),\n height: `calc(100% + ${TOP_OFFSET + TOP_ARC_SHIFT}px)`,\n }\n const svgStyle = styleObjectToCss(svgStyleObj)\n\n const paths = buildPathD(W, Y, N, R, STROKE_WIDTH, COLORS, TOP_ARC_SHIFT, Y_OFFSET, O_TOTAL, BORDER_EXTRA)\n\n const totalHeight = Y[Y.length - 1] ?? 0\n const totalWidth = Math.max(1, W + 2 * BORDER_EXTRA)\n const viewBoxHeight = totalHeight + TOP_OFFSET + TOP_ARC_SHIFT\n const viewBoxMinX = BORDER_EXTRA > 0 ? -BORDER_EXTRA : 0\n const viewBoxMinY = -STROKE_WIDTH * 2 - TOP_ARC_SHIFT\n const viewBoxStr = `${viewBoxMinX} ${viewBoxMinY} ${totalWidth} ${viewBoxHeight}`\n\n const n = Y.length - 1\n const halfBorderWidth = TOTAL_BORDER_WIDTH / 2\n const insetSide = TOTAL_BORDER_WIDTH - BORDER_EXTRA\n const sectionsPadding = []\n for (let i = 0; i < n; i++) {\n const top = halfBorderWidth\n const bottom = i === n - 1 ? 0 : halfBorderWidth\n const left = i % 2 === 0 ? insetSide : 0\n const right = i % 2 === 0 ? 0 : insetSide\n sectionsPadding.push({ top, right, bottom, left })\n }\n\n return {\n wrapperStyle,\n svgAttributes: {\n class: svgClassName,\n viewBox: viewBoxStr,\n style: svgStyle,\n },\n paths,\n sectionsPadding,\n }\n}\n\n/**\n * Measure wrapper and section elements to get width and section bottom Ys.\n * Children with the excludeClassName (default: same class used on the SVG by serpentineBorder) are excluded.\n * horizontalOverflow is resolved to pixels using strokeCount and strokeWidth.\n *\n * @param {HTMLElement} wrapperEl\n * @param {{\n * layoutMode: 'content' | 'border'\n * horizontalOverflow?: number | 'borderWidth' | 'halfBorderWidth'\n * strokeCount: number\n * strokeWidth: number\n * excludeClassName?: string\n * }} options\n * @returns {{ width: number, sectionBottomYs: number[] } | null}\n */\nexport function measureSections(wrapperEl, options) {\n const { layoutMode, horizontalOverflow = 0, strokeCount, strokeWidth, excludeClassName = DEFAULT_SVG_CLASS } = options\n const BORDER_EXTRA = resolveOverflowToPixels(horizontalOverflow, strokeCount, strokeWidth)\n if (!wrapperEl) return null\n\n const sectionEls = excludeClassName\n ? Array.from(wrapperEl.children).filter((el) => !el.classList.contains(excludeClassName))\n : Array.from(wrapperEl.children)\n\n if (sectionEls.length === 0) return null\n\n const rect = wrapperEl.getBoundingClientRect()\n const baseWidth = rect.width\n\n const W =\n layoutMode === 'border'\n ? Math.max(1, baseWidth - 2 * BORDER_EXTRA)\n : Math.max(1, baseWidth)\n\n const Y = [0]\n for (let i = 0; i < sectionEls.length; i++) {\n const r = sectionEls[i].getBoundingClientRect()\n Y.push(r.top - rect.top + r.height)\n }\n\n return { width: W, sectionBottomYs: Y }\n}\n","import { useEffect, useRef, useState } from 'react'\nimport { measureSections, serpentineBorder } from './serpentineCore.js'\n\nfunction cssStringToStyleObject(css) {\n const obj = {}\n if (!css || typeof css !== 'string') return obj\n for (const decl of css.split(';')) {\n const colon = decl.indexOf(':')\n if (colon === -1) continue\n const key = decl.slice(0, colon).trim().replace(/-([a-z])/g, (_, c) => c.toUpperCase())\n const value = decl.slice(colon + 1).trim()\n if (key) obj[key] = value\n }\n return obj\n}\n\nfunction pathAttrsForReact(attrs) {\n return Object.fromEntries(\n Object.entries(attrs).map(([k, v]) => [k === 'stroke-width' ? 'strokeWidth' : k, v])\n )\n}\n\nfunction SerpentineBorder({\n children,\n strokeCount,\n strokeWidth,\n radius,\n horizontalOverflow,\n colors,\n layoutMode,\n}) {\n const wrapperRef = useRef(null)\n const [borderData, setBorderData] = useState(null)\n\n useEffect(() => {\n const wrapper = wrapperRef.current\n if (!wrapper) return\n\n const measure = () => {\n const measured = measureSections(wrapper, {\n layoutMode,\n horizontalOverflow,\n strokeCount,\n strokeWidth,\n })\n if (!measured) return\n\n const data = serpentineBorder({\n width: measured.width,\n sectionBottomYs: measured.sectionBottomYs,\n strokeCount,\n strokeWidth,\n radius,\n horizontalOverflow,\n colors,\n layoutMode,\n })\n setBorderData(data)\n }\n\n measure()\n const ro = new ResizeObserver(measure)\n ro.observe(wrapper)\n return () => ro.disconnect()\n }, [strokeCount, strokeWidth, radius, horizontalOverflow, colors, layoutMode])\n\n return (\n <div\n ref={wrapperRef}\n className=\"serpentine-wrapper\"\n style={borderData?.wrapperStyle ?? { position: 'relative', boxSizing: 'border-box' }}\n data-testid=\"serpentine-wrapper\"\n >\n {borderData && (() => {\n const { class: className, style: styleStr, ...restSvgAttrs } = borderData.svgAttributes\n const style = typeof styleStr === 'string' ? cssStringToStyleObject(styleStr) : styleStr\n return (\n <svg\n data-testid=\"serpentine-svg\"\n className={className}\n style={style}\n {...restSvgAttrs}\n >\n {borderData.paths.map((pathAttributes, i) => (\n <path key={i} {...pathAttrsForReact(pathAttributes)} />\n ))}\n </svg>\n )\n })()}\n {children}\n </div>\n )\n}\n\nexport default SerpentineBorder\n"],"names":["DEFAULT_COLORS","resolveOverflowToPixels","horizontalOverflow","N","STROKE_WIDTH","totalBorderWidth","styleObjectToCss","obj","k","v","key","val","buildPathD","W","Y","R","COLORS","TOP_ARC_SHIFT","Y_OFFSET","O_TOTAL","BORDER_EXTRA","R1","RIGHT_EXTEND","n","parts","i","o","oj","r","rj","rj1","leftOffset","xLeft","rightExt","xRight","xLeftArc","xRightArc","xRightR1","yCurrTop","segs","t","yCurr","yNext","yExit","lastY","DEFAULT_SVG_CLASS","DEFAULTS","serpentineBorder","options","layoutMode","svgClassName","wrapperEl","measured","measureSections","TOTAL_BORDER_WIDTH","TOP_OFFSET","wrapperStyle","svgStyleObj","svgStyle","paths","totalHeight","totalWidth","viewBoxHeight","viewBoxMinX","viewBoxMinY","viewBoxStr","halfBorderWidth","insetSide","sectionsPadding","top","bottom","left","right","strokeCount","strokeWidth","excludeClassName","sectionEls","el","rect","baseWidth","cssStringToStyleObject","css","decl","colon","_","c","value","pathAttrsForReact","attrs","SerpentineBorder","children","radius","colors","wrapperRef","useRef","borderData","setBorderData","useState","useEffect","wrapper","measure","data","ro","jsxs","className","styleStr","restSvgAttrs","style","jsx","pathAttributes"],"mappings":"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"}
1
+ {"version":3,"file":"serpentine-border.js","sources":["../src/constants.js","../src/serpentineCore.js","../src/SerpentineBorder.jsx"],"sourcesContent":["export const DEFAULT_COLORS = ['#ffffff', '#000000']\n","/**\n * Vanilla JS core for serpentine border SVG generation.\n * Single export: call with measured dimensions and options to get everything needed to render.\n */\n\nimport { DEFAULT_COLORS } from './constants.js'\n\nfunction resolveOverflowToPixels(horizontalOverflow, N, STROKE_WIDTH) {\n if (typeof horizontalOverflow === 'number') return horizontalOverflow\n const totalBorderWidth = N * STROKE_WIDTH\n if (horizontalOverflow === 'borderWidth') return totalBorderWidth\n if (horizontalOverflow === 'halfBorderWidth') return totalBorderWidth / 2\n return 0\n}\n\nfunction styleObjectToCss(obj) {\n return Object.entries(obj)\n .map(([k, v]) => {\n const key = k.replace(/([A-Z])/g, '-$1').toLowerCase()\n const val = typeof v === 'number' && !Number.isNaN(v) ? `${v}px` : String(v)\n return `${key}: ${val}`\n })\n .join('; ')\n}\n\nfunction buildPathD(W, Y, N, R, STROKE_WIDTH, COLORS, TOP_ARC_SHIFT, Y_OFFSET, O_TOTAL, BORDER_EXTRA) {\n const R1 = STROKE_WIDTH * (N - 1)\n const RIGHT_EXTEND = STROKE_WIDTH / 2\n const n = Y.length - 1\n const parts = []\n for (let i = 0; i < N; i++) {\n const o = i * STROKE_WIDTH\n const j = N - 1 - i\n const oj = j * STROKE_WIDTH\n const r = R - o\n const rj = R - oj\n const r1 = R1 - o\n const rj1 = R1 - oj\n\n const leftOffset = O_TOTAL - BORDER_EXTRA + RIGHT_EXTEND\n const xLeft = o - O_TOTAL + leftOffset\n const rightExt = BORDER_EXTRA\n const xRight = W + rightExt - oj - RIGHT_EXTEND\n const xLeftArc = xLeft + (R - o)\n const xRightArc = W + rightExt - R - RIGHT_EXTEND\n const xRightR1 = W + rightExt - R1 - RIGHT_EXTEND\n\n const yCurrTop = O_TOTAL + Y_OFFSET\n const segs = [\n `M ${xRight} ${yCurrTop - R1 - STROKE_WIDTH / 2 - TOP_ARC_SHIFT}`,\n `L ${xRight} ${yCurrTop - R1 - TOP_ARC_SHIFT}`,\n `A ${rj1} ${rj1} 0 0 1 ${xRightR1} ${yCurrTop - oj - TOP_ARC_SHIFT}`,\n `L ${xLeftArc} ${o + Y_OFFSET - TOP_ARC_SHIFT}`,\n `A ${r} ${r} 0 0 0 ${xLeft} ${R + Y_OFFSET - TOP_ARC_SHIFT}`,\n `L ${xLeft} ${R + Y_OFFSET}`,\n ]\n\n for (let t = 0; t < n - 1; t++) {\n const yCurr = Y[t + 1]\n const yNext = Y[t + 2]\n const yExit = yCurr + R - O_TOTAL + Y_OFFSET\n\n if (t % 2 === 0) {\n segs.push(`L ${xLeft} ${yCurr - R + Y_OFFSET}`)\n segs.push(`A ${r} ${r} 0 0 0 ${xLeftArc} ${yCurr - o + Y_OFFSET}`)\n segs.push(`L ${xRightArc} ${yCurr - o + Y_OFFSET}`)\n segs.push(`A ${rj} ${rj} 0 0 1 ${xRight} ${yExit}`)\n segs.push(`L ${xRight} ${yNext - R + Y_OFFSET}`)\n } else {\n segs.push(`L ${xRight} ${yCurr - R + Y_OFFSET}`)\n segs.push(`A ${rj} ${rj} 0 0 1 ${xRightArc} ${yCurr - oj + Y_OFFSET}`)\n segs.push(`L ${xLeftArc} ${yCurr - oj + Y_OFFSET}`)\n segs.push(`A ${r} ${r} 0 0 0 ${xLeft} ${yExit}`)\n segs.push(`L ${xLeft} ${yNext - R + Y_OFFSET}`)\n }\n }\n\n const lastY = Y[n]\n if ((n - 2) % 2 === 0) {\n segs.push(`L ${xRight} ${lastY}`)\n } else {\n segs.push(`L ${xLeft} ${lastY}`)\n }\n parts.push({\n d: segs.join(' '),\n stroke: COLORS[i % COLORS.length],\n 'stroke-width': STROKE_WIDTH,\n fill: 'none',\n })\n }\n return parts\n}\n\nconst DEFAULT_SVG_CLASS = 'serpentine-border-svg'\n\nconst DEFAULTS = {\n strokeCount: 5,\n strokeWidth: 8,\n radius: 50,\n horizontalOverflow: 0,\n layoutMode: 'border',\n}\n\n/**\n * Compute everything needed to render the serpentine border.\n * Accepts either (width + sectionBottomYs) for pure/custom use, or wrapperEl to measure from the DOM.\n * When using wrapperEl, returns null in non-DOM environments (e.g. SSR) or when measurement fails.\n *\n * @param {{\n * width?: number\n * sectionBottomYs?: number[]\n * wrapperEl?: HTMLElement\n * strokeCount?: number\n * strokeWidth?: number\n * radius?: number\n * horizontalOverflow?: number | 'borderWidth' | 'halfBorderWidth'\n * colors?: string[]\n * layoutMode?: 'content' | 'border'\n * svgClassName?: string\n * }} options\n * @returns {{\n * wrapperStyle: Record<string, unknown>\n * svgAttributes: { class?: string, viewBox: string, style: string }\n * paths: Array<{ d: string, stroke: string, 'stroke-width': number, fill: string }>\n * } | null}\n */\nexport function serpentineBorder(options) {\n const N = options.strokeCount ?? DEFAULTS.strokeCount\n const STROKE_WIDTH = options.strokeWidth ?? DEFAULTS.strokeWidth\n const R = options.radius ?? DEFAULTS.radius\n const horizontalOverflow = options.horizontalOverflow ?? DEFAULTS.horizontalOverflow\n const COLORS = options.colors ?? DEFAULT_COLORS\n const layoutMode = options.layoutMode ?? DEFAULTS.layoutMode\n const svgClassName = options.svgClassName ?? DEFAULT_SVG_CLASS\n\n let W, Y\n if (options.wrapperEl != null) {\n const wrapperEl = options.wrapperEl\n const hasDOM = typeof document !== 'undefined' && typeof wrapperEl.getBoundingClientRect === 'function'\n if (!hasDOM) return null\n const measured = measureSections(wrapperEl, {\n layoutMode,\n horizontalOverflow,\n strokeCount: N,\n strokeWidth: STROKE_WIDTH,\n excludeClassName: svgClassName,\n })\n if (!measured) return null\n W = measured.width\n Y = measured.sectionBottomYs\n } else {\n if (options.width == null || options.sectionBottomYs == null) return null\n W = options.width\n Y = options.sectionBottomYs\n }\n\n const BORDER_EXTRA = resolveOverflowToPixels(horizontalOverflow, N, STROKE_WIDTH)\n const O_TOTAL = (N - 1) * STROKE_WIDTH\n const TOTAL_BORDER_WIDTH = N * STROKE_WIDTH\n const TOP_OFFSET = 2 * STROKE_WIDTH\n const Y_OFFSET = O_TOTAL / 2\n const TOP_ARC_SHIFT = ((N - 1) / 2) * STROKE_WIDTH + Y_OFFSET\n\n const wrapperStyle =\n layoutMode === 'border'\n ? {\n boxSizing: 'border-box',\n position: 'relative',\n marginTop: `${TOTAL_BORDER_WIDTH / 2}px`,\n ...(BORDER_EXTRA > 0 && {\n paddingLeft: `${BORDER_EXTRA}px`,\n paddingRight: `${BORDER_EXTRA}px`,\n }),\n }\n : {\n position: 'relative',\n boxSizing: 'border-box',\n }\n\n const svgStyleObj =\n layoutMode === 'border'\n ? {\n position: 'absolute',\n overflow: 'hidden',\n ...(BORDER_EXTRA > 0\n ? { width: '100%', left: 0 }\n : { width: `calc(100% + ${2 * BORDER_EXTRA}px)`, left: -BORDER_EXTRA }),\n top: -(TOP_OFFSET + TOP_ARC_SHIFT),\n height: `calc(100% + ${TOP_OFFSET + TOP_ARC_SHIFT}px)`,\n }\n : {\n position: 'absolute',\n overflow: 'hidden',\n width: `calc(100% + ${2 * BORDER_EXTRA}px)`,\n left: -BORDER_EXTRA,\n top: -(TOP_OFFSET + TOP_ARC_SHIFT),\n height: `calc(100% + ${TOP_OFFSET + TOP_ARC_SHIFT}px)`,\n }\n const svgStyle = styleObjectToCss(svgStyleObj)\n\n const paths = buildPathD(W, Y, N, R, STROKE_WIDTH, COLORS, TOP_ARC_SHIFT, Y_OFFSET, O_TOTAL, BORDER_EXTRA)\n\n const totalHeight = Y[Y.length - 1] ?? 0\n const totalWidth = Math.max(1, W + 2 * BORDER_EXTRA)\n const viewBoxHeight = totalHeight + TOP_OFFSET + TOP_ARC_SHIFT\n const viewBoxMinX = BORDER_EXTRA > 0 ? -BORDER_EXTRA : 0\n const viewBoxMinY = -STROKE_WIDTH * 2 - TOP_ARC_SHIFT\n const viewBoxStr = `${viewBoxMinX} ${viewBoxMinY} ${totalWidth} ${viewBoxHeight}`\n\n return {\n wrapperStyle,\n svgAttributes: {\n class: svgClassName,\n viewBox: viewBoxStr,\n style: svgStyle,\n },\n paths,\n }\n}\n\n/**\n * Compute padding for each section so content does not overlap the border.\n * Returns an object with even, odd, and last: use for even-indexed sections, odd-indexed sections, and the last section respectively.\n *\n * @param {{\n * sectionCount: number\n * strokeCount: number\n * strokeWidth: number\n * horizontalOverflow?: number | 'borderWidth' | 'halfBorderWidth'\n * }} options\n * @returns {{ even: { top: number, right: number, bottom: number, left: number }, odd: { top: number, right: number, bottom: number, left: number }, last: { top: number, right: number, bottom: number, left: number } }}\n */\nexport function getSectionsPadding(options) {\n const { sectionCount: n, strokeCount: N, strokeWidth: STROKE_WIDTH } = options\n const horizontalOverflow = options.horizontalOverflow ?? 0\n const TOTAL_BORDER_WIDTH = N * STROKE_WIDTH\n const BORDER_EXTRA = resolveOverflowToPixels(horizontalOverflow, N, STROKE_WIDTH)\n const halfBorderWidth = TOTAL_BORDER_WIDTH / 2\n const insetSide = TOTAL_BORDER_WIDTH - BORDER_EXTRA\n const even = { top: halfBorderWidth, right: 0, bottom: halfBorderWidth, left: insetSide }\n const odd = { top: halfBorderWidth, right: insetSide, bottom: halfBorderWidth, left: 0 }\n const lastIsEven = n > 0 && (n - 1) % 2 === 0\n const last = {\n top: halfBorderWidth,\n right: lastIsEven ? 0 : insetSide,\n bottom: 0,\n left: lastIsEven ? insetSide : 0,\n }\n return { even, odd, last }\n}\n\n/**\n * Measure wrapper and section elements to get width and section bottom Ys.\n * Children with the excludeClassName (default: same class used on the SVG by serpentineBorder) are excluded.\n * horizontalOverflow is resolved to pixels using strokeCount and strokeWidth.\n *\n * @param {HTMLElement} wrapperEl\n * @param {{\n * layoutMode: 'content' | 'border'\n * horizontalOverflow?: number | 'borderWidth' | 'halfBorderWidth'\n * strokeCount: number\n * strokeWidth: number\n * excludeClassName?: string\n * }} options\n * @returns {{ width: number, sectionBottomYs: number[] } | null}\n */\nexport function measureSections(wrapperEl, options) {\n const { layoutMode, horizontalOverflow = 0, strokeCount, strokeWidth, excludeClassName = DEFAULT_SVG_CLASS } = options\n const BORDER_EXTRA = resolveOverflowToPixels(horizontalOverflow, strokeCount, strokeWidth)\n if (!wrapperEl) return null\n\n const sectionEls = excludeClassName\n ? Array.from(wrapperEl.children).filter((el) => !el.classList.contains(excludeClassName))\n : Array.from(wrapperEl.children)\n\n if (sectionEls.length === 0) return null\n\n const rect = wrapperEl.getBoundingClientRect()\n const baseWidth = rect.width\n\n const W =\n layoutMode === 'border'\n ? Math.max(1, baseWidth - 2 * BORDER_EXTRA)\n : Math.max(1, baseWidth)\n\n const Y = [0]\n for (let i = 0; i < sectionEls.length; i++) {\n const r = sectionEls[i].getBoundingClientRect()\n Y.push(r.top - rect.top + r.height)\n }\n\n return { width: W, sectionBottomYs: Y }\n}\n","import { useEffect, useRef, useState } from 'react'\nimport { measureSections, serpentineBorder } from './serpentineCore.js'\n\nfunction cssStringToStyleObject(css) {\n const obj = {}\n if (!css || typeof css !== 'string') return obj\n for (const decl of css.split(';')) {\n const colon = decl.indexOf(':')\n if (colon === -1) continue\n const key = decl.slice(0, colon).trim().replace(/-([a-z])/g, (_, c) => c.toUpperCase())\n const value = decl.slice(colon + 1).trim()\n if (key) obj[key] = value\n }\n return obj\n}\n\nfunction pathAttrsForReact(attrs) {\n return Object.fromEntries(\n Object.entries(attrs).map(([k, v]) => [k === 'stroke-width' ? 'strokeWidth' : k, v])\n )\n}\n\nfunction SerpentineBorder({\n children,\n strokeCount,\n strokeWidth,\n radius,\n horizontalOverflow,\n colors,\n layoutMode,\n}) {\n const wrapperRef = useRef(null)\n const [borderData, setBorderData] = useState(null)\n\n useEffect(() => {\n const wrapper = wrapperRef.current\n if (!wrapper) return\n\n const measure = () => {\n const measured = measureSections(wrapper, {\n layoutMode,\n horizontalOverflow,\n strokeCount,\n strokeWidth,\n })\n if (!measured) return\n\n const data = serpentineBorder({\n width: measured.width,\n sectionBottomYs: measured.sectionBottomYs,\n strokeCount,\n strokeWidth,\n radius,\n horizontalOverflow,\n colors,\n layoutMode,\n })\n setBorderData(data)\n }\n\n measure()\n const ro = new ResizeObserver(measure)\n ro.observe(wrapper)\n return () => ro.disconnect()\n }, [strokeCount, strokeWidth, radius, horizontalOverflow, colors, layoutMode])\n\n return (\n <div\n ref={wrapperRef}\n className=\"serpentine-wrapper\"\n style={borderData?.wrapperStyle ?? { position: 'relative', boxSizing: 'border-box' }}\n data-testid=\"serpentine-wrapper\"\n >\n {borderData && (() => {\n const { class: className, style: styleStr, ...restSvgAttrs } = borderData.svgAttributes\n const style = typeof styleStr === 'string' ? cssStringToStyleObject(styleStr) : styleStr\n return (\n <svg\n data-testid=\"serpentine-svg\"\n className={className}\n style={style}\n {...restSvgAttrs}\n >\n {borderData.paths.map((pathAttributes, i) => (\n <path key={i} {...pathAttrsForReact(pathAttributes)} />\n ))}\n </svg>\n )\n })()}\n {children}\n </div>\n )\n}\n\nexport default SerpentineBorder\n"],"names":["DEFAULT_COLORS","resolveOverflowToPixels","horizontalOverflow","N","STROKE_WIDTH","totalBorderWidth","styleObjectToCss","obj","k","v","key","val","buildPathD","W","Y","R","COLORS","TOP_ARC_SHIFT","Y_OFFSET","O_TOTAL","BORDER_EXTRA","R1","RIGHT_EXTEND","n","parts","i","o","oj","r","rj","rj1","leftOffset","xLeft","rightExt","xRight","xLeftArc","xRightArc","xRightR1","yCurrTop","segs","t","yCurr","yNext","yExit","lastY","DEFAULT_SVG_CLASS","DEFAULTS","serpentineBorder","options","layoutMode","svgClassName","wrapperEl","measured","measureSections","TOTAL_BORDER_WIDTH","TOP_OFFSET","wrapperStyle","svgStyleObj","svgStyle","paths","totalHeight","totalWidth","viewBoxHeight","viewBoxMinX","viewBoxMinY","viewBoxStr","getSectionsPadding","halfBorderWidth","insetSide","even","odd","lastIsEven","strokeCount","strokeWidth","excludeClassName","sectionEls","el","rect","baseWidth","cssStringToStyleObject","css","decl","colon","_","c","value","pathAttrsForReact","attrs","SerpentineBorder","children","radius","colors","wrapperRef","useRef","borderData","setBorderData","useState","useEffect","wrapper","measure","data","ro","jsxs","className","styleStr","restSvgAttrs","style","jsx","pathAttributes"],"mappings":"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"}
package/index.d.ts CHANGED
@@ -40,7 +40,21 @@ export interface SerpentineBorderResult {
40
40
  wrapperStyle: Record<string, unknown>
41
41
  svgAttributes: { class?: string; viewBox: string; style: string }
42
42
  paths: Array<{ d: string; stroke: string; 'stroke-width': number; fill: string }>
43
- sectionsPadding: SectionPadding[]
44
43
  }
45
44
 
45
+ export interface GetSectionsPaddingOptions {
46
+ sectionCount: number
47
+ strokeCount: number
48
+ strokeWidth: number
49
+ horizontalOverflow?: number | 'borderWidth' | 'halfBorderWidth'
50
+ }
51
+
52
+ export interface SectionsPaddingMap {
53
+ even: SectionPadding
54
+ odd: SectionPadding
55
+ last: SectionPadding
56
+ }
57
+
58
+ export declare function getSectionsPadding(options: GetSectionsPaddingOptions): SectionsPaddingMap
59
+
46
60
  export declare function serpentineBorder(options: SerpentineBorderOptions): SerpentineBorderResult | null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serpentine-border",
3
- "version": "2.0.0",
3
+ "version": "3.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",