dingbatch 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # dingbatch
2
+
3
+ Parametric MCM (Mid-Century Modern) dingbat shapes as SVGs. Curated presets with full customization.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install dingbatch
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Basic - Use Presets
14
+
15
+ ```ts
16
+ import { boomerang } from 'dingbatch';
17
+
18
+ // Get a preset shape
19
+ const shape = boomerang.crescent();
20
+
21
+ // Returns:
22
+ // {
23
+ // path: "M...", // SVG path d attribute
24
+ // width: 100, // Bounding width
25
+ // height: 80, // Bounding height
26
+ // viewBox: "0 0 100 80",
27
+ // svg: "<svg>...</svg>" // Complete SVG string
28
+ // }
29
+ ```
30
+
31
+ ### Available Presets
32
+
33
+ ```ts
34
+ boomerang.crescent() // Curved arc/moon shape
35
+ boomerang.fatMoon() // Wide crescent, rounded tips
36
+ boomerang.sharpChevron() // Solid V-shape arrow
37
+ boomerang.thinArc() // Narrow curved arc
38
+ boomerang.horseshoe() // U-shape
39
+ ```
40
+
41
+ ### With Overrides
42
+
43
+ ```ts
44
+ // Rotate a preset
45
+ boomerang.crescent({ rotation: 45 })
46
+
47
+ // Change any parameter
48
+ boomerang.fatMoon({ thickness: 60, taper: 0.8 })
49
+ ```
50
+
51
+ ### Full Custom
52
+
53
+ ```ts
54
+ boomerang({
55
+ style: 'crescent',
56
+ armLength: 50,
57
+ bendAngle: 160,
58
+ thickness: 35,
59
+ taper: 0.5,
60
+ tipRoundness: 0.2,
61
+ rotation: 0,
62
+ })
63
+ ```
64
+
65
+ ## React Components
66
+
67
+ ```tsx
68
+ import { Dingbat, Boomerang } from 'dingbatch/react';
69
+ import { boomerang } from 'dingbatch';
70
+
71
+ // Generic component - pass any shape
72
+ <Dingbat shape={boomerang.crescent()} fill="#D4A03D" />
73
+
74
+ // Shape-specific component
75
+ <Boomerang preset="crescent" fill="#D4A03D" />
76
+ <Boomerang preset="fatMoon" rotation={45} className="hero-deco" />
77
+
78
+ // Custom params
79
+ <Boomerang
80
+ params={{ style: 'chevron', armLength: 60, bendAngle: 70, thickness: 30, taper: 1, tipRoundness: 0 }}
81
+ fill="#1A1A1A"
82
+ />
83
+ ```
84
+
85
+ ### Dingbat Props
86
+
87
+ | Prop | Type | Default | Description |
88
+ |------|------|---------|-------------|
89
+ | `shape` | `DingbatResult` | required | Shape object from generator |
90
+ | `fill` | `string` | `'currentColor'` | Fill color |
91
+ | `stroke` | `string` | `'none'` | Stroke color |
92
+ | `strokeWidth` | `number` | `0` | Stroke width |
93
+ | `className` | `string` | - | CSS class |
94
+ | `style` | `CSSProperties` | - | Inline styles |
95
+
96
+ ### Boomerang Props
97
+
98
+ | Prop | Type | Default | Description |
99
+ |------|------|---------|-------------|
100
+ | `preset` | `string` | `'crescent'` | Preset name |
101
+ | `params` | `BoomerangParams` | - | Full custom params (overrides preset) |
102
+ | `rotation` | `number` | - | Rotation in degrees |
103
+ | + all Dingbat props | | | |
104
+
105
+ ## Boomerang Styles
106
+
107
+ The boomerang generator supports 4 geometric styles:
108
+
109
+ - **crescent** - Curved arc/moon shape
110
+ - **classic** - Traditional boomerang with two diverging arms
111
+ - **horseshoe** - U-shape with parallel arms
112
+ - **chevron** - Solid V-shape or arrow
113
+
114
+ ## Parameters
115
+
116
+ | Parameter | Type | Description |
117
+ |-----------|------|-------------|
118
+ | `style` | `'classic' \| 'crescent' \| 'horseshoe' \| 'chevron'` | Geometry mode |
119
+ | `armLength` | `number` | Length of arms or arc radius |
120
+ | `bendAngle` | `number` | Angle between arms or arc span |
121
+ | `thickness` | `number` | Shape thickness |
122
+ | `taper` | `number` | Tip thickness ratio (0=pointed, 1=uniform) |
123
+ | `tipRoundness` | `number` | How rounded the tips are (0-1) |
124
+ | `armCurvature` | `number` | Curve amount for classic/horseshoe (0-1) |
125
+ | `bendSharpness` | `number` | Bend smoothness for classic (0-1) |
126
+ | `armBalance` | `number` | Asymmetric arm lengths (0.5-2) |
127
+ | `rotation` | `number` | Rotation in degrees |
128
+
129
+ ## TypeScript
130
+
131
+ Full TypeScript support with exported types:
132
+
133
+ ```ts
134
+ import type {
135
+ DingbatResult,
136
+ BoomerangParams,
137
+ BoomerangStyle
138
+ } from 'dingbatch';
139
+
140
+ import type {
141
+ DingbatProps,
142
+ BoomerangProps
143
+ } from 'dingbatch/react';
144
+ ```
145
+
146
+ ## License
147
+
148
+ MIT
@@ -0,0 +1,531 @@
1
+ // src/generators/utils/svg.ts
2
+ function wrapPath(path, width, height) {
3
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}"><path d="${path}" fill="currentColor"/></svg>`;
4
+ }
5
+ function addSvgFields(result) {
6
+ const viewBox = `0 0 ${result.width} ${result.height}`;
7
+ const svg = wrapPath(result.path, result.width, result.height);
8
+ return { ...result, viewBox, svg };
9
+ }
10
+
11
+ // src/presets/boomerang.ts
12
+ var crescent = {
13
+ style: "crescent",
14
+ armLength: 50,
15
+ bendAngle: 160,
16
+ thickness: 35,
17
+ taper: 0.5,
18
+ tipRoundness: 0.2,
19
+ armCurvature: 0,
20
+ bendSharpness: 0
21
+ };
22
+ var fatMoon = {
23
+ style: "crescent",
24
+ armLength: 45,
25
+ bendAngle: 180,
26
+ thickness: 50,
27
+ taper: 0.3,
28
+ tipRoundness: 0.6,
29
+ armCurvature: 0,
30
+ bendSharpness: 0
31
+ };
32
+ var sharpChevron = {
33
+ style: "chevron",
34
+ armLength: 55,
35
+ bendAngle: 60,
36
+ thickness: 40,
37
+ taper: 1,
38
+ tipRoundness: 0,
39
+ armCurvature: 0,
40
+ bendSharpness: 0
41
+ };
42
+ var thinArc = {
43
+ style: "crescent",
44
+ armLength: 60,
45
+ bendAngle: 140,
46
+ thickness: 20,
47
+ taper: 0.7,
48
+ tipRoundness: 0.1,
49
+ armCurvature: 0,
50
+ bendSharpness: 0
51
+ };
52
+ var horseshoe = {
53
+ style: "horseshoe",
54
+ armLength: 80,
55
+ bendAngle: 50,
56
+ thickness: 18,
57
+ taper: 0.2,
58
+ tipRoundness: 0.3,
59
+ armCurvature: 0.4,
60
+ bendSharpness: 0
61
+ };
62
+
63
+ // src/generators/curved/boomerang.ts
64
+ function boomerangBase(params) {
65
+ const style = params.style || "classic";
66
+ switch (style) {
67
+ case "crescent":
68
+ return generateCrescent(params);
69
+ case "horseshoe":
70
+ return generateHorseshoe(params);
71
+ case "chevron":
72
+ return generateChevron(params);
73
+ case "classic":
74
+ default:
75
+ return generateClassic(params);
76
+ }
77
+ }
78
+ var boomerang = Object.assign(boomerangBase, {
79
+ crescent: (overrides) => boomerangBase({ ...crescent, ...overrides }),
80
+ fatMoon: (overrides) => boomerangBase({ ...fatMoon, ...overrides }),
81
+ sharpChevron: (overrides) => boomerangBase({ ...sharpChevron, ...overrides }),
82
+ thinArc: (overrides) => boomerangBase({ ...thinArc, ...overrides }),
83
+ horseshoe: (overrides) => boomerangBase({ ...horseshoe, ...overrides })
84
+ });
85
+ function generateClassic(params) {
86
+ const {
87
+ armLength,
88
+ bendAngle,
89
+ armCurvature,
90
+ bendSharpness,
91
+ thickness,
92
+ taper,
93
+ tipRoundness,
94
+ armBalance = 1,
95
+ thicknessBalance = 0,
96
+ rotation = 0
97
+ } = params;
98
+ const clampedBendAngle = Math.max(30, Math.min(180, bendAngle));
99
+ const clampedArmCurvature = Math.max(0, Math.min(1, armCurvature));
100
+ const clampedBendSharpness = Math.max(0, Math.min(1, bendSharpness));
101
+ const clampedTaper = Math.max(0, Math.min(1, taper));
102
+ const clampedTipRoundness = Math.max(0, Math.min(1, tipRoundness));
103
+ const clampedArmBalance = Math.max(0.5, Math.min(2, armBalance));
104
+ const clampedThicknessBalance = Math.max(-1, Math.min(1, thicknessBalance));
105
+ const arm1Length = armLength * (2 / (1 + clampedArmBalance));
106
+ const arm2Length = armLength * (2 * clampedArmBalance / (1 + clampedArmBalance));
107
+ const halfAngle = clampedBendAngle / 2 * Math.PI / 180;
108
+ const arm1Dir = { x: -Math.sin(halfAngle), y: -Math.cos(halfAngle) };
109
+ const arm2Dir = { x: Math.sin(halfAngle), y: -Math.cos(halfAngle) };
110
+ const tip1 = { x: arm1Dir.x * arm1Length, y: arm1Dir.y * arm1Length };
111
+ const tip2 = { x: arm2Dir.x * arm2Length, y: arm2Dir.y * arm2Length };
112
+ const bendPoint = { x: 0, y: 0 };
113
+ const numSamples = 20;
114
+ const arm1Spine = generateArmSpine(tip1, bendPoint, arm1Dir, arm1Length, clampedArmCurvature, numSamples);
115
+ const arm2Spine = generateArmSpine(bendPoint, tip2, arm2Dir, arm2Length, clampedArmCurvature, numSamples);
116
+ const minThickness = thickness * clampedTaper;
117
+ function getThicknessAlongArm(t, isArm1) {
118
+ const tipFade = Math.sin(t * Math.PI / 2);
119
+ const baseThickness = minThickness + (thickness - minThickness) * tipFade;
120
+ const balanceFactor = isArm1 ? 1 - clampedThicknessBalance * 0.3 : 1 + clampedThicknessBalance * 0.3;
121
+ return baseThickness * balanceFactor;
122
+ }
123
+ const arm1Outer = [];
124
+ const arm1Inner = [];
125
+ const arm2Outer = [];
126
+ const arm2Inner = [];
127
+ for (let i = 0; i < arm1Spine.length; i++) {
128
+ const t = i / (arm1Spine.length - 1);
129
+ const point = arm1Spine[i];
130
+ const tangent = getSpineTangent(arm1Spine, i);
131
+ const normal = { x: -tangent.y, y: tangent.x };
132
+ const halfThick = getThicknessAlongArm(t, true) / 2;
133
+ arm1Outer.push({ x: point.x + normal.x * halfThick, y: point.y + normal.y * halfThick });
134
+ arm1Inner.push({ x: point.x - normal.x * halfThick, y: point.y - normal.y * halfThick });
135
+ }
136
+ for (let i = 0; i < arm2Spine.length; i++) {
137
+ const t = 1 - i / (arm2Spine.length - 1);
138
+ const point = arm2Spine[i];
139
+ const tangent = getSpineTangent(arm2Spine, i);
140
+ const normal = { x: -tangent.y, y: tangent.x };
141
+ const halfThick = getThicknessAlongArm(t, false) / 2;
142
+ arm2Outer.push({ x: point.x + normal.x * halfThick, y: point.y + normal.y * halfThick });
143
+ arm2Inner.push({ x: point.x - normal.x * halfThick, y: point.y - normal.y * halfThick });
144
+ }
145
+ const pathParts = [];
146
+ pathParts.push(`M ${arm1Outer[0].x.toFixed(2)},${arm1Outer[0].y.toFixed(2)}`);
147
+ const tip1Curve = clampedTipRoundness * thickness * 0.3;
148
+ pathParts.push(`Q ${(tip1.x + arm1Dir.x * tip1Curve).toFixed(2)},${(tip1.y + arm1Dir.y * tip1Curve).toFixed(2)} ${arm1Inner[0].x.toFixed(2)},${arm1Inner[0].y.toFixed(2)}`);
149
+ pathParts.push(smoothCurveThroughPoints(arm1Inner));
150
+ const bendInner2 = arm2Inner[0];
151
+ if (clampedBendSharpness < 0.3) {
152
+ pathParts.push(`L ${bendInner2.x.toFixed(2)},${bendInner2.y.toFixed(2)}`);
153
+ } else {
154
+ const bendInnerOffset = thickness * 0.2 * clampedBendSharpness;
155
+ const bendInnerCtrl = { x: 0, y: bendInnerOffset };
156
+ pathParts.push(`Q ${bendInnerCtrl.x.toFixed(2)},${bendInnerCtrl.y.toFixed(2)} ${bendInner2.x.toFixed(2)},${bendInner2.y.toFixed(2)}`);
157
+ }
158
+ pathParts.push(smoothCurveThroughPoints(arm2Inner));
159
+ const tip2Curve = clampedTipRoundness * thickness * 0.3;
160
+ pathParts.push(`Q ${(tip2.x + arm2Dir.x * tip2Curve).toFixed(2)},${(tip2.y + arm2Dir.y * tip2Curve).toFixed(2)} ${arm2Outer[arm2Outer.length - 1].x.toFixed(2)},${arm2Outer[arm2Outer.length - 1].y.toFixed(2)}`);
161
+ const arm2OuterReversed = [...arm2Outer].reverse();
162
+ pathParts.push(smoothCurveThroughPoints(arm2OuterReversed));
163
+ const bendOuter1 = arm1Outer[arm1Outer.length - 1];
164
+ if (clampedBendSharpness < 0.3) {
165
+ pathParts.push(`L ${bendOuter1.x.toFixed(2)},${bendOuter1.y.toFixed(2)}`);
166
+ } else {
167
+ const bendOuterOffset = -thickness * 0.4 * clampedBendSharpness;
168
+ const bendOuterCtrl = { x: 0, y: bendOuterOffset };
169
+ pathParts.push(`Q ${bendOuterCtrl.x.toFixed(2)},${bendOuterCtrl.y.toFixed(2)} ${bendOuter1.x.toFixed(2)},${bendOuter1.y.toFixed(2)}`);
170
+ }
171
+ const arm1OuterReversed = [...arm1Outer].reverse();
172
+ pathParts.push(smoothCurveThroughPoints(arm1OuterReversed));
173
+ pathParts.push("Z");
174
+ let path = pathParts.join(" ");
175
+ const allPoints = [...arm1Outer, ...arm1Inner, ...arm2Outer, ...arm2Inner];
176
+ let minX = Math.min(...allPoints.map((p) => p.x));
177
+ let maxX = Math.max(...allPoints.map((p) => p.x));
178
+ let minY = Math.min(...allPoints.map((p) => p.y));
179
+ let maxY = Math.max(...allPoints.map((p) => p.y));
180
+ const padding = thickness * 0.3 * clampedTipRoundness;
181
+ minX -= padding;
182
+ maxX += padding;
183
+ minY -= padding;
184
+ const width = maxX - minX;
185
+ const height = maxY - minY;
186
+ path = normalizePath(path, minX, minY);
187
+ if (rotation !== 0) {
188
+ path = rotatePath(path, rotation, width, height);
189
+ }
190
+ return addSvgFields({ path, width, height, centerX: width / 2, centerY: height / 2 });
191
+ }
192
+ function generateCrescent(params) {
193
+ const {
194
+ armLength,
195
+ // Used as arc radius
196
+ bendAngle,
197
+ // Used as arc span (degrees, 30-300)
198
+ thickness,
199
+ taper,
200
+ tipRoundness,
201
+ armBalance = 1,
202
+ // Asymmetric arc thickness along length
203
+ rotation = 0
204
+ } = params;
205
+ const radius = Math.max(20, armLength);
206
+ const arcSpan = Math.max(30, Math.min(300, bendAngle)) * Math.PI / 180;
207
+ const clampedTaper = Math.max(0, Math.min(1, taper));
208
+ const clampedTipRoundness = Math.max(0, Math.min(1, tipRoundness));
209
+ const clampedArmBalance = Math.max(0.5, Math.min(2, armBalance));
210
+ const numSamples = 30;
211
+ const startAngle = -arcSpan / 2;
212
+ const endAngle = arcSpan / 2;
213
+ const spine = [];
214
+ for (let i = 0; i <= numSamples; i++) {
215
+ const t = i / numSamples;
216
+ const angle = startAngle + (endAngle - startAngle) * t;
217
+ spine.push({
218
+ x: Math.sin(angle) * radius,
219
+ y: -Math.cos(angle) * radius + radius
220
+ // Shift so bottom of arc is at y=0
221
+ });
222
+ }
223
+ const minThickness = thickness * clampedTaper;
224
+ function getThicknessAtT(t) {
225
+ const balanceShift = (t - 0.5) * (clampedArmBalance - 1) * 0.5;
226
+ const adjustedT = Math.max(0, Math.min(1, t + balanceShift));
227
+ const adjustedDist = Math.min(adjustedT, 1 - adjustedT) * 2;
228
+ const adjustedFactor = Math.sin(adjustedDist * Math.PI / 2);
229
+ return minThickness + (thickness - minThickness) * adjustedFactor;
230
+ }
231
+ const outer = [];
232
+ const inner = [];
233
+ for (let i = 0; i < spine.length; i++) {
234
+ const t = i / (spine.length - 1);
235
+ const point = spine[i];
236
+ const tangent = getSpineTangent(spine, i);
237
+ const normal = { x: -tangent.y, y: tangent.x };
238
+ const halfThick = getThicknessAtT(t) / 2;
239
+ outer.push({ x: point.x + normal.x * halfThick, y: point.y + normal.y * halfThick });
240
+ inner.push({ x: point.x - normal.x * halfThick, y: point.y - normal.y * halfThick });
241
+ }
242
+ const pathParts = [];
243
+ pathParts.push(`M ${outer[0].x.toFixed(2)},${outer[0].y.toFixed(2)}`);
244
+ const startTipCurve = clampedTipRoundness * thickness * 0.4;
245
+ const startTangent = getSpineTangent(spine, 0);
246
+ pathParts.push(`Q ${(spine[0].x - startTangent.x * startTipCurve).toFixed(2)},${(spine[0].y - startTangent.y * startTipCurve).toFixed(2)} ${inner[0].x.toFixed(2)},${inner[0].y.toFixed(2)}`);
247
+ pathParts.push(smoothCurveThroughPoints(inner));
248
+ const endTipCurve = clampedTipRoundness * thickness * 0.4;
249
+ const endTangent = getSpineTangent(spine, spine.length - 1);
250
+ pathParts.push(`Q ${(spine[spine.length - 1].x + endTangent.x * endTipCurve).toFixed(2)},${(spine[spine.length - 1].y + endTangent.y * endTipCurve).toFixed(2)} ${outer[outer.length - 1].x.toFixed(2)},${outer[outer.length - 1].y.toFixed(2)}`);
251
+ const outerReversed = [...outer].reverse();
252
+ pathParts.push(smoothCurveThroughPoints(outerReversed));
253
+ pathParts.push("Z");
254
+ let path = pathParts.join(" ");
255
+ const allPoints = [...outer, ...inner];
256
+ let minX = Math.min(...allPoints.map((p) => p.x));
257
+ let maxX = Math.max(...allPoints.map((p) => p.x));
258
+ let minY = Math.min(...allPoints.map((p) => p.y));
259
+ let maxY = Math.max(...allPoints.map((p) => p.y));
260
+ const padding = thickness * 0.3 * clampedTipRoundness;
261
+ minX -= padding;
262
+ maxX += padding;
263
+ minY -= padding;
264
+ maxY += padding;
265
+ const width = maxX - minX;
266
+ const height = maxY - minY;
267
+ path = normalizePath(path, minX, minY);
268
+ if (rotation !== 0) {
269
+ path = rotatePath(path, rotation, width, height);
270
+ }
271
+ return addSvgFields({ path, width, height, centerX: width / 2, centerY: height / 2 });
272
+ }
273
+ function generateHorseshoe(params) {
274
+ const {
275
+ armLength,
276
+ // Length of the vertical arms
277
+ bendAngle,
278
+ // Spacing between arms (width at top)
279
+ armCurvature,
280
+ // How curved the bottom connection is
281
+ thickness,
282
+ taper,
283
+ tipRoundness,
284
+ armBalance = 1,
285
+ // Asymmetric arm lengths
286
+ rotation = 0
287
+ } = params;
288
+ const spacing = Math.max(20, bendAngle);
289
+ const clampedArmCurvature = Math.max(0, Math.min(1, armCurvature));
290
+ const clampedTaper = Math.max(0, Math.min(1, taper));
291
+ const clampedTipRoundness = Math.max(0, Math.min(1, tipRoundness));
292
+ const clampedArmBalance = Math.max(0.5, Math.min(2, armBalance));
293
+ const arm1Len = armLength * (2 / (1 + clampedArmBalance));
294
+ const arm2Len = armLength * (2 * clampedArmBalance / (1 + clampedArmBalance));
295
+ const halfSpacing = spacing / 2;
296
+ const halfThick = thickness / 2;
297
+ const tipThick = thickness * clampedTaper / 2;
298
+ const leftOuterX = -halfSpacing - halfThick;
299
+ const leftInnerX = -halfSpacing + halfThick;
300
+ const rightInnerX = halfSpacing - halfThick;
301
+ const rightOuterX = halfSpacing + halfThick;
302
+ const leftTopY = 0;
303
+ const leftBottomY = arm1Len;
304
+ const rightBottomY = arm2Len;
305
+ const rightTopY = 0;
306
+ const bottomY = Math.max(arm1Len, arm2Len);
307
+ const curveDepth = halfSpacing * clampedArmCurvature;
308
+ const pathParts = [];
309
+ const leftTipOuterX = -halfSpacing - tipThick;
310
+ const leftTipInnerX = -halfSpacing + tipThick;
311
+ const rightTipInnerX = halfSpacing - tipThick;
312
+ const rightTipOuterX = halfSpacing + tipThick;
313
+ pathParts.push(`M ${leftTipOuterX.toFixed(2)},${leftTopY.toFixed(2)}`);
314
+ if (clampedTipRoundness > 0) {
315
+ const tipCurve = clampedTipRoundness * thickness * 0.3;
316
+ pathParts.push(`Q ${(-halfSpacing).toFixed(2)},${(leftTopY - tipCurve).toFixed(2)} ${leftTipInnerX.toFixed(2)},${leftTopY.toFixed(2)}`);
317
+ } else {
318
+ pathParts.push(`L ${leftTipInnerX.toFixed(2)},${leftTopY.toFixed(2)}`);
319
+ }
320
+ pathParts.push(`L ${leftInnerX.toFixed(2)},${leftBottomY.toFixed(2)}`);
321
+ if (clampedArmCurvature > 0.1) {
322
+ const innerCtrlY = bottomY + curveDepth;
323
+ pathParts.push(`Q ${0},${innerCtrlY.toFixed(2)} ${rightInnerX.toFixed(2)},${rightBottomY.toFixed(2)}`);
324
+ } else {
325
+ pathParts.push(`L ${rightInnerX.toFixed(2)},${rightBottomY.toFixed(2)}`);
326
+ }
327
+ pathParts.push(`L ${rightTipInnerX.toFixed(2)},${rightTopY.toFixed(2)}`);
328
+ if (clampedTipRoundness > 0) {
329
+ const tipCurve = clampedTipRoundness * thickness * 0.3;
330
+ pathParts.push(`Q ${halfSpacing.toFixed(2)},${(rightTopY - tipCurve).toFixed(2)} ${rightTipOuterX.toFixed(2)},${rightTopY.toFixed(2)}`);
331
+ } else {
332
+ pathParts.push(`L ${rightTipOuterX.toFixed(2)},${rightTopY.toFixed(2)}`);
333
+ }
334
+ pathParts.push(`L ${rightOuterX.toFixed(2)},${rightBottomY.toFixed(2)}`);
335
+ if (clampedArmCurvature > 0.1) {
336
+ const outerCtrlY = bottomY + curveDepth + thickness;
337
+ pathParts.push(`Q ${0},${outerCtrlY.toFixed(2)} ${leftOuterX.toFixed(2)},${leftBottomY.toFixed(2)}`);
338
+ } else {
339
+ pathParts.push(`L ${leftOuterX.toFixed(2)},${leftBottomY.toFixed(2)}`);
340
+ }
341
+ pathParts.push(`L ${leftTipOuterX.toFixed(2)},${leftTopY.toFixed(2)}`);
342
+ pathParts.push("Z");
343
+ let path = pathParts.join(" ");
344
+ const padding = thickness * 0.3;
345
+ let minX = leftOuterX - padding;
346
+ let maxX = rightOuterX + padding;
347
+ let minY = -padding - clampedTipRoundness * thickness * 0.3;
348
+ let maxY = bottomY + curveDepth + thickness + padding;
349
+ const width = maxX - minX;
350
+ const height = maxY - minY;
351
+ path = normalizePath(path, minX, minY);
352
+ if (rotation !== 0) {
353
+ path = rotatePath(path, rotation, width, height);
354
+ }
355
+ return addSvgFields({ path, width, height, centerX: width / 2, centerY: height / 2 });
356
+ }
357
+ function generateChevron(params) {
358
+ const {
359
+ armLength,
360
+ // Length of the arms
361
+ bendAngle,
362
+ // Angle of the V (smaller = sharper point)
363
+ thickness,
364
+ // Controls arm width (proportional)
365
+ taper = 0,
366
+ // 0 = V with cutout, 1 = solid triangle (no cutout)
367
+ tipRoundness = 0,
368
+ // Roundness at the tip
369
+ armBalance = 1,
370
+ // Asymmetric arm lengths
371
+ rotation = 0
372
+ } = params;
373
+ const clampedBendAngle = Math.max(20, Math.min(160, bendAngle));
374
+ const clampedTaper = Math.max(0, Math.min(1, taper));
375
+ const clampedTipRoundness = Math.max(0, Math.min(1, tipRoundness));
376
+ const clampedArmBalance = Math.max(0.5, Math.min(2, armBalance));
377
+ const armWidth = Math.max(armLength * 0.15, thickness * 0.5);
378
+ const cutoutDepth = armLength * 0.6 * (1 - clampedTaper);
379
+ const arm1Len = armLength * (2 / (1 + clampedArmBalance));
380
+ const arm2Len = armLength * (2 * clampedArmBalance / (1 + clampedArmBalance));
381
+ const halfAngle = clampedBendAngle / 2 * Math.PI / 180;
382
+ const tip = { x: 0, y: 0 };
383
+ const arm1End = { x: -Math.sin(halfAngle) * arm1Len, y: -Math.cos(halfAngle) * arm1Len };
384
+ const arm2End = { x: Math.sin(halfAngle) * arm2Len, y: -Math.cos(halfAngle) * arm2Len };
385
+ const arm1Perp = { x: -Math.cos(halfAngle), y: Math.sin(halfAngle) };
386
+ const arm2Perp = { x: Math.cos(halfAngle), y: Math.sin(halfAngle) };
387
+ const arm1OuterCorner = {
388
+ x: arm1End.x + arm1Perp.x * armWidth / 2,
389
+ y: arm1End.y + arm1Perp.y * armWidth / 2
390
+ };
391
+ const arm1InnerCorner = {
392
+ x: arm1End.x - arm1Perp.x * armWidth / 2,
393
+ y: arm1End.y - arm1Perp.y * armWidth / 2
394
+ };
395
+ const arm2OuterCorner = {
396
+ x: arm2End.x + arm2Perp.x * armWidth / 2,
397
+ y: arm2End.y + arm2Perp.y * armWidth / 2
398
+ };
399
+ const arm2InnerCorner = {
400
+ x: arm2End.x - arm2Perp.x * armWidth / 2,
401
+ y: arm2End.y - arm2Perp.y * armWidth / 2
402
+ };
403
+ const innerTip = {
404
+ x: 0,
405
+ y: -cutoutDepth
406
+ };
407
+ const pathParts = [];
408
+ if (clampedTipRoundness > 0) {
409
+ const roundAmount = clampedTipRoundness * armWidth * 0.3;
410
+ const tipStart = {
411
+ x: tip.x + arm1Perp.x * roundAmount,
412
+ y: tip.y + arm1Perp.y * roundAmount - roundAmount * 0.5
413
+ };
414
+ pathParts.push(`M ${tipStart.x.toFixed(2)},${tipStart.y.toFixed(2)}`);
415
+ const tipEnd = {
416
+ x: tip.x + arm2Perp.x * roundAmount,
417
+ y: tip.y + arm2Perp.y * roundAmount - roundAmount * 0.5
418
+ };
419
+ pathParts.push(`Q ${tip.x.toFixed(2)},${tip.y.toFixed(2)} ${tipEnd.x.toFixed(2)},${tipEnd.y.toFixed(2)}`);
420
+ } else {
421
+ pathParts.push(`M ${tip.x.toFixed(2)},${tip.y.toFixed(2)}`);
422
+ }
423
+ pathParts.push(`L ${arm2OuterCorner.x.toFixed(2)},${arm2OuterCorner.y.toFixed(2)}`);
424
+ pathParts.push(`L ${arm2InnerCorner.x.toFixed(2)},${arm2InnerCorner.y.toFixed(2)}`);
425
+ if (cutoutDepth > 5 && clampedTaper < 0.9) {
426
+ pathParts.push(`L ${innerTip.x.toFixed(2)},${innerTip.y.toFixed(2)}`);
427
+ }
428
+ pathParts.push(`L ${arm1InnerCorner.x.toFixed(2)},${arm1InnerCorner.y.toFixed(2)}`);
429
+ pathParts.push(`L ${arm1OuterCorner.x.toFixed(2)},${arm1OuterCorner.y.toFixed(2)}`);
430
+ pathParts.push("Z");
431
+ let path = pathParts.join(" ");
432
+ const allPoints = [tip, arm1End, arm2End, arm1OuterCorner, arm1InnerCorner, arm2OuterCorner, arm2InnerCorner];
433
+ if (cutoutDepth > 5 && clampedTaper < 0.9) {
434
+ allPoints.push(innerTip);
435
+ }
436
+ let minX = Math.min(...allPoints.map((p) => p.x));
437
+ let maxX = Math.max(...allPoints.map((p) => p.x));
438
+ let minY = Math.min(...allPoints.map((p) => p.y));
439
+ let maxY = Math.max(...allPoints.map((p) => p.y));
440
+ const padding = armWidth * 0.2;
441
+ minX -= padding;
442
+ maxX += padding;
443
+ minY -= padding;
444
+ maxY += padding;
445
+ const width = maxX - minX;
446
+ const height = maxY - minY;
447
+ path = normalizePath(path, minX, minY);
448
+ if (rotation !== 0) {
449
+ path = rotatePath(path, rotation, width, height);
450
+ }
451
+ return addSvgFields({ path, width, height, centerX: width / 2, centerY: height / 2 });
452
+ }
453
+ function generateArmSpine(start, end, direction, length, curvature, numSamples) {
454
+ const points = [];
455
+ const perpDir = { x: direction.y, y: -direction.x };
456
+ const bowAmount = length * 0.4 * curvature;
457
+ const midPoint = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 };
458
+ const controlPoint = {
459
+ x: midPoint.x + perpDir.x * bowAmount,
460
+ y: midPoint.y + perpDir.y * bowAmount
461
+ };
462
+ for (let i = 0; i <= numSamples; i++) {
463
+ const t = i / numSamples;
464
+ if (curvature < 0.1) {
465
+ points.push({
466
+ x: start.x + (end.x - start.x) * t,
467
+ y: start.y + (end.y - start.y) * t
468
+ });
469
+ } else {
470
+ const mt = 1 - t;
471
+ points.push({
472
+ x: mt * mt * start.x + 2 * mt * t * controlPoint.x + t * t * end.x,
473
+ y: mt * mt * start.y + 2 * mt * t * controlPoint.y + t * t * end.y
474
+ });
475
+ }
476
+ }
477
+ return points;
478
+ }
479
+ function getSpineTangent(spine, index) {
480
+ const prev = spine[Math.max(0, index - 1)];
481
+ const next = spine[Math.min(spine.length - 1, index + 1)];
482
+ const dx = next.x - prev.x;
483
+ const dy = next.y - prev.y;
484
+ const len = Math.sqrt(dx * dx + dy * dy);
485
+ return len > 0 ? { x: dx / len, y: dy / len } : { x: 1, y: 0 };
486
+ }
487
+ function smoothCurveThroughPoints(points) {
488
+ if (points.length < 2) return "";
489
+ const parts = [];
490
+ const tension = 0.3;
491
+ for (let i = 0; i < points.length - 1; i++) {
492
+ const p0 = points[Math.max(0, i - 1)];
493
+ const p1 = points[i];
494
+ const p2 = points[i + 1];
495
+ const p3 = points[Math.min(points.length - 1, i + 2)];
496
+ const cp1x = p1.x + (p2.x - p0.x) * tension;
497
+ const cp1y = p1.y + (p2.y - p0.y) * tension;
498
+ const cp2x = p2.x - (p3.x - p1.x) * tension;
499
+ const cp2y = p2.y - (p3.y - p1.y) * tension;
500
+ parts.push(`C ${cp1x.toFixed(2)},${cp1y.toFixed(2)} ${cp2x.toFixed(2)},${cp2y.toFixed(2)} ${p2.x.toFixed(2)},${p2.y.toFixed(2)}`);
501
+ }
502
+ return parts.join(" ");
503
+ }
504
+ function normalizePath(path, minX, minY) {
505
+ return path.replace(
506
+ /([-\d.]+),([-\d.]+)/g,
507
+ (_, x, y) => `${(parseFloat(x) - minX).toFixed(2)},${(parseFloat(y) - minY).toFixed(2)}`
508
+ );
509
+ }
510
+ function rotatePath(path, rotation, width, height) {
511
+ const rad = rotation * Math.PI / 180;
512
+ const cos = Math.cos(rad);
513
+ const sin = Math.sin(rad);
514
+ const cx = width / 2;
515
+ const cy = height / 2;
516
+ return path.replace(
517
+ /([-\d.]+),([-\d.]+)/g,
518
+ (_, xStr, yStr) => {
519
+ const x = parseFloat(xStr) - cx;
520
+ const y = parseFloat(yStr) - cy;
521
+ const rx = x * cos - y * sin + cx;
522
+ const ry = x * sin + y * cos + cy;
523
+ return `${rx.toFixed(2)},${ry.toFixed(2)}`;
524
+ }
525
+ );
526
+ }
527
+
528
+ export {
529
+ boomerang
530
+ };
531
+ //# sourceMappingURL=chunk-IAFZ3FNR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/generators/utils/svg.ts","../src/presets/boomerang.ts","../src/generators/curved/boomerang.ts"],"sourcesContent":["// src/generators/utils/svg.ts\n\ninterface BaseResult {\n path: string;\n width: number;\n height: number;\n centerX: number;\n centerY: number;\n}\n\nexport function wrapPath(path: string, width: number, height: number): string {\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 ${width} ${height}\" width=\"${width}\" height=\"${height}\"><path d=\"${path}\" fill=\"currentColor\"/></svg>`;\n}\n\nexport function addSvgFields<T extends BaseResult>(result: T): T & { viewBox: string; svg: string } {\n const viewBox = `0 0 ${result.width} ${result.height}`;\n const svg = wrapPath(result.path, result.width, result.height);\n return { ...result, viewBox, svg };\n}\n","import type { BoomerangParams } from '../generators/curved/boomerang';\n\nexport const crescent: BoomerangParams = {\n style: 'crescent',\n armLength: 50,\n bendAngle: 160,\n thickness: 35,\n taper: 0.5,\n tipRoundness: 0.2,\n armCurvature: 0,\n bendSharpness: 0,\n};\n\nexport const fatMoon: BoomerangParams = {\n style: 'crescent',\n armLength: 45,\n bendAngle: 180,\n thickness: 50,\n taper: 0.3,\n tipRoundness: 0.6,\n armCurvature: 0,\n bendSharpness: 0,\n};\n\nexport const sharpChevron: BoomerangParams = {\n style: 'chevron',\n armLength: 55,\n bendAngle: 60,\n thickness: 40,\n taper: 1,\n tipRoundness: 0,\n armCurvature: 0,\n bendSharpness: 0,\n};\n\nexport const thinArc: BoomerangParams = {\n style: 'crescent',\n armLength: 60,\n bendAngle: 140,\n thickness: 20,\n taper: 0.7,\n tipRoundness: 0.1,\n armCurvature: 0,\n bendSharpness: 0,\n};\n\nexport const horseshoe: BoomerangParams = {\n style: 'horseshoe',\n armLength: 80,\n bendAngle: 50,\n thickness: 18,\n taper: 0.2,\n tipRoundness: 0.3,\n armCurvature: 0.4,\n bendSharpness: 0,\n};\n","import { GeneratorResult } from '../types';\nimport { addSvgFields } from '../utils/svg';\nimport * as presets from '../../presets/boomerang';\n\nexport type BoomerangStyle = 'classic' | 'crescent' | 'horseshoe' | 'chevron';\n\nexport interface BoomerangParams {\n // Style selection\n style?: BoomerangStyle; // 'classic' (outline V), 'crescent' (arc), 'horseshoe' (U), 'chevron' (solid V)\n\n // Core shape - interpreted based on style\n armLength: number; // Length of arms (classic/horseshoe) or arc radius (crescent)\n bendAngle: number; // Angle between arms (classic), arc span (crescent), arm spacing (horseshoe)\n armCurvature: number; // How curved: 0-1 (classic: arm bow, crescent: unused, horseshoe: bottom curve)\n bendSharpness: number; // Bend smoothness: 0-1\n\n // Thickness\n thickness: number; // Max thickness at widest point\n taper: number; // Tip thickness ratio: 0-1 (0 = pointed, 1 = uniform)\n tipRoundness: number; // How rounded the tips are: 0-1\n\n // Asymmetry (optional)\n armBalance?: number; // Ratio of arm1/arm2 length: 0.5-2 (default 1)\n thicknessBalance?: number; // Thickness bias: -1 to 1 (default 0)\n\n // Orientation\n rotation?: number; // Rotation in degrees (default 0)\n}\n\n/**\n * Boomerang function with preset methods attached.\n */\ninterface BoomerangFunction {\n (params: BoomerangParams): GeneratorResult;\n crescent: (overrides?: Partial<BoomerangParams>) => GeneratorResult;\n fatMoon: (overrides?: Partial<BoomerangParams>) => GeneratorResult;\n sharpChevron: (overrides?: Partial<BoomerangParams>) => GeneratorResult;\n thinArc: (overrides?: Partial<BoomerangParams>) => GeneratorResult;\n horseshoe: (overrides?: Partial<BoomerangParams>) => GeneratorResult;\n}\n\n/**\n * Unified boomerang generator with multiple geometry modes:\n * - 'classic': Two arms diverging from a central bend (V-shape, traditional boomerang)\n * - 'crescent': Single curved arc with thickness (moon-like, thick C-curve)\n * - 'horseshoe': Two parallel arms connected by curved bottom (U-shape)\n */\nfunction boomerangBase(params: BoomerangParams): GeneratorResult {\n const style = params.style || 'classic';\n\n switch (style) {\n case 'crescent':\n return generateCrescent(params);\n case 'horseshoe':\n return generateHorseshoe(params);\n case 'chevron':\n return generateChevron(params);\n case 'classic':\n default:\n return generateClassic(params);\n }\n}\n\nexport const boomerang: BoomerangFunction = Object.assign(boomerangBase, {\n crescent: (overrides?: Partial<BoomerangParams>) =>\n boomerangBase({ ...presets.crescent, ...overrides }),\n fatMoon: (overrides?: Partial<BoomerangParams>) =>\n boomerangBase({ ...presets.fatMoon, ...overrides }),\n sharpChevron: (overrides?: Partial<BoomerangParams>) =>\n boomerangBase({ ...presets.sharpChevron, ...overrides }),\n thinArc: (overrides?: Partial<BoomerangParams>) =>\n boomerangBase({ ...presets.thinArc, ...overrides }),\n horseshoe: (overrides?: Partial<BoomerangParams>) =>\n boomerangBase({ ...presets.horseshoe, ...overrides }),\n});\n\n// ============================================================================\n// CLASSIC STYLE - Two arms diverging from bend point (V-shape)\n// ============================================================================\n\nfunction generateClassic(params: BoomerangParams): GeneratorResult {\n const {\n armLength,\n bendAngle,\n armCurvature,\n bendSharpness,\n thickness,\n taper,\n tipRoundness,\n armBalance = 1,\n thicknessBalance = 0,\n rotation = 0,\n } = params;\n\n const clampedBendAngle = Math.max(30, Math.min(180, bendAngle));\n const clampedArmCurvature = Math.max(0, Math.min(1, armCurvature));\n const clampedBendSharpness = Math.max(0, Math.min(1, bendSharpness));\n const clampedTaper = Math.max(0, Math.min(1, taper));\n const clampedTipRoundness = Math.max(0, Math.min(1, tipRoundness));\n const clampedArmBalance = Math.max(0.5, Math.min(2, armBalance));\n const clampedThicknessBalance = Math.max(-1, Math.min(1, thicknessBalance));\n\n const arm1Length = armLength * (2 / (1 + clampedArmBalance));\n const arm2Length = armLength * (2 * clampedArmBalance / (1 + clampedArmBalance));\n\n const halfAngle = (clampedBendAngle / 2) * Math.PI / 180;\n\n const arm1Dir = { x: -Math.sin(halfAngle), y: -Math.cos(halfAngle) };\n const arm2Dir = { x: Math.sin(halfAngle), y: -Math.cos(halfAngle) };\n\n const tip1 = { x: arm1Dir.x * arm1Length, y: arm1Dir.y * arm1Length };\n const tip2 = { x: arm2Dir.x * arm2Length, y: arm2Dir.y * arm2Length };\n const bendPoint = { x: 0, y: 0 };\n\n const numSamples = 20;\n\n const arm1Spine = generateArmSpine(tip1, bendPoint, arm1Dir, arm1Length, clampedArmCurvature, numSamples);\n const arm2Spine = generateArmSpine(bendPoint, tip2, arm2Dir, arm2Length, clampedArmCurvature, numSamples);\n\n const minThickness = thickness * clampedTaper;\n\n function getThicknessAlongArm(t: number, isArm1: boolean): number {\n const tipFade = Math.sin(t * Math.PI / 2);\n const baseThickness = minThickness + (thickness - minThickness) * tipFade;\n const balanceFactor = isArm1\n ? 1 - clampedThicknessBalance * 0.3\n : 1 + clampedThicknessBalance * 0.3;\n return baseThickness * balanceFactor;\n }\n\n const arm1Outer: Point[] = [];\n const arm1Inner: Point[] = [];\n const arm2Outer: Point[] = [];\n const arm2Inner: Point[] = [];\n\n for (let i = 0; i < arm1Spine.length; i++) {\n const t = i / (arm1Spine.length - 1);\n const point = arm1Spine[i];\n const tangent = getSpineTangent(arm1Spine, i);\n const normal = { x: -tangent.y, y: tangent.x };\n const halfThick = getThicknessAlongArm(t, true) / 2;\n\n arm1Outer.push({ x: point.x + normal.x * halfThick, y: point.y + normal.y * halfThick });\n arm1Inner.push({ x: point.x - normal.x * halfThick, y: point.y - normal.y * halfThick });\n }\n\n for (let i = 0; i < arm2Spine.length; i++) {\n const t = 1 - i / (arm2Spine.length - 1);\n const point = arm2Spine[i];\n const tangent = getSpineTangent(arm2Spine, i);\n const normal = { x: -tangent.y, y: tangent.x };\n const halfThick = getThicknessAlongArm(t, false) / 2;\n\n arm2Outer.push({ x: point.x + normal.x * halfThick, y: point.y + normal.y * halfThick });\n arm2Inner.push({ x: point.x - normal.x * halfThick, y: point.y - normal.y * halfThick });\n }\n\n const pathParts: string[] = [];\n\n pathParts.push(`M ${arm1Outer[0].x.toFixed(2)},${arm1Outer[0].y.toFixed(2)}`);\n\n const tip1Curve = clampedTipRoundness * thickness * 0.3;\n pathParts.push(`Q ${(tip1.x + arm1Dir.x * tip1Curve).toFixed(2)},${(tip1.y + arm1Dir.y * tip1Curve).toFixed(2)} ${arm1Inner[0].x.toFixed(2)},${arm1Inner[0].y.toFixed(2)}`);\n\n pathParts.push(smoothCurveThroughPoints(arm1Inner));\n\n const bendInner2 = arm2Inner[0];\n\n if (clampedBendSharpness < 0.3) {\n pathParts.push(`L ${bendInner2.x.toFixed(2)},${bendInner2.y.toFixed(2)}`);\n } else {\n const bendInnerOffset = thickness * 0.2 * clampedBendSharpness;\n const bendInnerCtrl = { x: 0, y: bendInnerOffset };\n pathParts.push(`Q ${bendInnerCtrl.x.toFixed(2)},${bendInnerCtrl.y.toFixed(2)} ${bendInner2.x.toFixed(2)},${bendInner2.y.toFixed(2)}`);\n }\n\n pathParts.push(smoothCurveThroughPoints(arm2Inner));\n\n const tip2Curve = clampedTipRoundness * thickness * 0.3;\n pathParts.push(`Q ${(tip2.x + arm2Dir.x * tip2Curve).toFixed(2)},${(tip2.y + arm2Dir.y * tip2Curve).toFixed(2)} ${arm2Outer[arm2Outer.length - 1].x.toFixed(2)},${arm2Outer[arm2Outer.length - 1].y.toFixed(2)}`);\n\n const arm2OuterReversed = [...arm2Outer].reverse();\n pathParts.push(smoothCurveThroughPoints(arm2OuterReversed));\n\n const bendOuter1 = arm1Outer[arm1Outer.length - 1];\n\n if (clampedBendSharpness < 0.3) {\n pathParts.push(`L ${bendOuter1.x.toFixed(2)},${bendOuter1.y.toFixed(2)}`);\n } else {\n const bendOuterOffset = -thickness * 0.4 * clampedBendSharpness;\n const bendOuterCtrl = { x: 0, y: bendOuterOffset };\n pathParts.push(`Q ${bendOuterCtrl.x.toFixed(2)},${bendOuterCtrl.y.toFixed(2)} ${bendOuter1.x.toFixed(2)},${bendOuter1.y.toFixed(2)}`);\n }\n\n const arm1OuterReversed = [...arm1Outer].reverse();\n pathParts.push(smoothCurveThroughPoints(arm1OuterReversed));\n\n pathParts.push('Z');\n\n let path = pathParts.join(' ');\n\n const allPoints = [...arm1Outer, ...arm1Inner, ...arm2Outer, ...arm2Inner];\n let minX = Math.min(...allPoints.map(p => p.x));\n let maxX = Math.max(...allPoints.map(p => p.x));\n let minY = Math.min(...allPoints.map(p => p.y));\n let maxY = Math.max(...allPoints.map(p => p.y));\n\n const padding = thickness * 0.3 * clampedTipRoundness;\n minX -= padding;\n maxX += padding;\n minY -= padding;\n\n const width = maxX - minX;\n const height = maxY - minY;\n\n path = normalizePath(path, minX, minY);\n\n if (rotation !== 0) {\n path = rotatePath(path, rotation, width, height);\n }\n\n return addSvgFields({ path, width, height, centerX: width / 2, centerY: height / 2 });\n}\n\n// ============================================================================\n// CRESCENT STYLE - Single curved arc with thickness (moon-like shape)\n// ============================================================================\n\nfunction generateCrescent(params: BoomerangParams): GeneratorResult {\n const {\n armLength, // Used as arc radius\n bendAngle, // Used as arc span (degrees, 30-300)\n thickness,\n taper,\n tipRoundness,\n armBalance = 1, // Asymmetric arc thickness along length\n rotation = 0,\n } = params;\n\n const radius = Math.max(20, armLength);\n const arcSpan = Math.max(30, Math.min(300, bendAngle)) * Math.PI / 180;\n const clampedTaper = Math.max(0, Math.min(1, taper));\n const clampedTipRoundness = Math.max(0, Math.min(1, tipRoundness));\n const clampedArmBalance = Math.max(0.5, Math.min(2, armBalance));\n\n const numSamples = 30;\n const startAngle = -arcSpan / 2;\n const endAngle = arcSpan / 2;\n\n // Generate spine points along the arc\n const spine: Point[] = [];\n for (let i = 0; i <= numSamples; i++) {\n const t = i / numSamples;\n const angle = startAngle + (endAngle - startAngle) * t;\n spine.push({\n x: Math.sin(angle) * radius,\n y: -Math.cos(angle) * radius + radius, // Shift so bottom of arc is at y=0\n });\n }\n\n // Calculate thickness at each point\n const minThickness = thickness * clampedTaper;\n\n function getThicknessAtT(t: number): number {\n // Taper at both ends, thickest in middle\n // Apply balance (shift where thickness is max)\n const balanceShift = (t - 0.5) * (clampedArmBalance - 1) * 0.5;\n const adjustedT = Math.max(0, Math.min(1, t + balanceShift));\n const adjustedDist = Math.min(adjustedT, 1 - adjustedT) * 2;\n const adjustedFactor = Math.sin(adjustedDist * Math.PI / 2);\n\n return minThickness + (thickness - minThickness) * adjustedFactor;\n }\n\n // Build inner and outer edges\n const outer: Point[] = [];\n const inner: Point[] = [];\n\n for (let i = 0; i < spine.length; i++) {\n const t = i / (spine.length - 1);\n const point = spine[i];\n const tangent = getSpineTangent(spine, i);\n const normal = { x: -tangent.y, y: tangent.x };\n const halfThick = getThicknessAtT(t) / 2;\n\n outer.push({ x: point.x + normal.x * halfThick, y: point.y + normal.y * halfThick });\n inner.push({ x: point.x - normal.x * halfThick, y: point.y - normal.y * halfThick });\n }\n\n // Build path\n const pathParts: string[] = [];\n\n // Start at first outer point\n pathParts.push(`M ${outer[0].x.toFixed(2)},${outer[0].y.toFixed(2)}`);\n\n // Rounded start tip\n const startTipCurve = clampedTipRoundness * thickness * 0.4;\n const startTangent = getSpineTangent(spine, 0);\n pathParts.push(`Q ${(spine[0].x - startTangent.x * startTipCurve).toFixed(2)},${(spine[0].y - startTangent.y * startTipCurve).toFixed(2)} ${inner[0].x.toFixed(2)},${inner[0].y.toFixed(2)}`);\n\n // Inner edge (start to end)\n pathParts.push(smoothCurveThroughPoints(inner));\n\n // Rounded end tip\n const endTipCurve = clampedTipRoundness * thickness * 0.4;\n const endTangent = getSpineTangent(spine, spine.length - 1);\n pathParts.push(`Q ${(spine[spine.length - 1].x + endTangent.x * endTipCurve).toFixed(2)},${(spine[spine.length - 1].y + endTangent.y * endTipCurve).toFixed(2)} ${outer[outer.length - 1].x.toFixed(2)},${outer[outer.length - 1].y.toFixed(2)}`);\n\n // Outer edge (end back to start)\n const outerReversed = [...outer].reverse();\n pathParts.push(smoothCurveThroughPoints(outerReversed));\n\n pathParts.push('Z');\n\n let path = pathParts.join(' ');\n\n // Calculate bounds\n const allPoints = [...outer, ...inner];\n let minX = Math.min(...allPoints.map(p => p.x));\n let maxX = Math.max(...allPoints.map(p => p.x));\n let minY = Math.min(...allPoints.map(p => p.y));\n let maxY = Math.max(...allPoints.map(p => p.y));\n\n const padding = thickness * 0.3 * clampedTipRoundness;\n minX -= padding;\n maxX += padding;\n minY -= padding;\n maxY += padding;\n\n const width = maxX - minX;\n const height = maxY - minY;\n\n path = normalizePath(path, minX, minY);\n\n if (rotation !== 0) {\n path = rotatePath(path, rotation, width, height);\n }\n\n return addSvgFields({ path, width, height, centerX: width / 2, centerY: height / 2 });\n}\n\n// ============================================================================\n// HORSESHOE STYLE - Two parallel arms connected by curved bottom (U-shape)\n// ============================================================================\n\nfunction generateHorseshoe(params: BoomerangParams): GeneratorResult {\n const {\n armLength, // Length of the vertical arms\n bendAngle, // Spacing between arms (width at top)\n armCurvature, // How curved the bottom connection is\n thickness,\n taper,\n tipRoundness,\n armBalance = 1, // Asymmetric arm lengths\n rotation = 0,\n } = params;\n\n const spacing = Math.max(20, bendAngle);\n const clampedArmCurvature = Math.max(0, Math.min(1, armCurvature));\n const clampedTaper = Math.max(0, Math.min(1, taper));\n const clampedTipRoundness = Math.max(0, Math.min(1, tipRoundness));\n const clampedArmBalance = Math.max(0.5, Math.min(2, armBalance));\n\n const arm1Len = armLength * (2 / (1 + clampedArmBalance));\n const arm2Len = armLength * (2 * clampedArmBalance / (1 + clampedArmBalance));\n\n const halfSpacing = spacing / 2;\n const halfThick = thickness / 2;\n const tipThick = thickness * clampedTaper / 2;\n\n // Use explicit geometry for straight parallel arms\n // Left arm outer/inner x-coordinates\n const leftOuterX = -halfSpacing - halfThick;\n const leftInnerX = -halfSpacing + halfThick;\n // Right arm outer/inner x-coordinates\n const rightInnerX = halfSpacing - halfThick;\n const rightOuterX = halfSpacing + halfThick;\n\n // Arm y-coordinates (with taper at tips)\n const leftTopY = 0;\n const leftBottomY = arm1Len;\n const rightBottomY = arm2Len;\n const rightTopY = 0;\n\n // Bottom curve parameters\n const bottomY = Math.max(arm1Len, arm2Len);\n const curveDepth = halfSpacing * clampedArmCurvature;\n\n // Build path directly with explicit coordinates\n const pathParts: string[] = [];\n\n // Start at left arm outer top (with tip width adjustment)\n const leftTipOuterX = -halfSpacing - tipThick;\n const leftTipInnerX = -halfSpacing + tipThick;\n const rightTipInnerX = halfSpacing - tipThick;\n const rightTipOuterX = halfSpacing + tipThick;\n\n pathParts.push(`M ${leftTipOuterX.toFixed(2)},${leftTopY.toFixed(2)}`);\n\n // Rounded left tip\n if (clampedTipRoundness > 0) {\n const tipCurve = clampedTipRoundness * thickness * 0.3;\n pathParts.push(`Q ${(-halfSpacing).toFixed(2)},${(leftTopY - tipCurve).toFixed(2)} ${leftTipInnerX.toFixed(2)},${leftTopY.toFixed(2)}`);\n } else {\n pathParts.push(`L ${leftTipInnerX.toFixed(2)},${leftTopY.toFixed(2)}`);\n }\n\n // Inner left arm - straight down\n pathParts.push(`L ${leftInnerX.toFixed(2)},${leftBottomY.toFixed(2)}`);\n\n // Inner bottom curve (from left to right)\n if (clampedArmCurvature > 0.1) {\n // Curved bottom using quadratic bezier\n const innerCtrlY = bottomY + curveDepth;\n pathParts.push(`Q ${0},${innerCtrlY.toFixed(2)} ${rightInnerX.toFixed(2)},${rightBottomY.toFixed(2)}`);\n } else {\n // Flat bottom\n pathParts.push(`L ${rightInnerX.toFixed(2)},${rightBottomY.toFixed(2)}`);\n }\n\n // Inner right arm - straight up\n pathParts.push(`L ${rightTipInnerX.toFixed(2)},${rightTopY.toFixed(2)}`);\n\n // Rounded right tip\n if (clampedTipRoundness > 0) {\n const tipCurve = clampedTipRoundness * thickness * 0.3;\n pathParts.push(`Q ${(halfSpacing).toFixed(2)},${(rightTopY - tipCurve).toFixed(2)} ${rightTipOuterX.toFixed(2)},${rightTopY.toFixed(2)}`);\n } else {\n pathParts.push(`L ${rightTipOuterX.toFixed(2)},${rightTopY.toFixed(2)}`);\n }\n\n // Outer right arm - straight down\n pathParts.push(`L ${rightOuterX.toFixed(2)},${rightBottomY.toFixed(2)}`);\n\n // Outer bottom curve (from right to left)\n if (clampedArmCurvature > 0.1) {\n const outerCtrlY = bottomY + curveDepth + thickness;\n pathParts.push(`Q ${0},${outerCtrlY.toFixed(2)} ${leftOuterX.toFixed(2)},${leftBottomY.toFixed(2)}`);\n } else {\n pathParts.push(`L ${leftOuterX.toFixed(2)},${leftBottomY.toFixed(2)}`);\n }\n\n // Outer left arm - straight up back to start\n pathParts.push(`L ${leftTipOuterX.toFixed(2)},${leftTopY.toFixed(2)}`);\n\n pathParts.push('Z');\n\n let path = pathParts.join(' ');\n\n // Calculate bounds from explicit geometry\n const padding = thickness * 0.3;\n let minX = leftOuterX - padding;\n let maxX = rightOuterX + padding;\n let minY = -padding - (clampedTipRoundness * thickness * 0.3);\n let maxY = bottomY + curveDepth + thickness + padding;\n\n const width = maxX - minX;\n const height = maxY - minY;\n\n path = normalizePath(path, minX, minY);\n\n if (rotation !== 0) {\n path = rotatePath(path, rotation, width, height);\n }\n\n return addSvgFields({ path, width, height, centerX: width / 2, centerY: height / 2 });\n}\n\n// ============================================================================\n// CHEVRON STYLE - Solid V-shape with inner cutout (like < or > arrows)\n// ============================================================================\n\nfunction generateChevron(params: BoomerangParams): GeneratorResult {\n const {\n armLength, // Length of the arms\n bendAngle, // Angle of the V (smaller = sharper point)\n thickness, // Controls arm width (proportional)\n taper = 0, // 0 = V with cutout, 1 = solid triangle (no cutout)\n tipRoundness = 0, // Roundness at the tip\n armBalance = 1, // Asymmetric arm lengths\n rotation = 0,\n } = params;\n\n const clampedBendAngle = Math.max(20, Math.min(160, bendAngle));\n const clampedTaper = Math.max(0, Math.min(1, taper));\n const clampedTipRoundness = Math.max(0, Math.min(1, tipRoundness));\n const clampedArmBalance = Math.max(0.5, Math.min(2, armBalance));\n\n // Arm width based on thickness, with minimum for visibility\n const armWidth = Math.max(armLength * 0.15, thickness * 0.5);\n\n // Cutout depth: taper controls this. High taper = no cutout (solid triangle)\n // taper=0: full cutout based on armLength\n // taper=1: no cutout (solid triangle)\n const cutoutDepth = armLength * 0.6 * (1 - clampedTaper);\n\n // Calculate arm lengths\n const arm1Len = armLength * (2 / (1 + clampedArmBalance));\n const arm2Len = armLength * (2 * clampedArmBalance / (1 + clampedArmBalance));\n\n const halfAngle = (clampedBendAngle / 2) * Math.PI / 180;\n\n // Outer points (tip and arm ends)\n const tip = { x: 0, y: 0 };\n const arm1End = { x: -Math.sin(halfAngle) * arm1Len, y: -Math.cos(halfAngle) * arm1Len };\n const arm2End = { x: Math.sin(halfAngle) * arm2Len, y: -Math.cos(halfAngle) * arm2Len };\n\n // Perpendicular directions for arm widths\n const arm1Perp = { x: -Math.cos(halfAngle), y: Math.sin(halfAngle) };\n const arm2Perp = { x: Math.cos(halfAngle), y: Math.sin(halfAngle) };\n\n // Outer corners at arm ends\n const arm1OuterCorner = {\n x: arm1End.x + arm1Perp.x * armWidth / 2,\n y: arm1End.y + arm1Perp.y * armWidth / 2\n };\n const arm1InnerCorner = {\n x: arm1End.x - arm1Perp.x * armWidth / 2,\n y: arm1End.y - arm1Perp.y * armWidth / 2\n };\n const arm2OuterCorner = {\n x: arm2End.x + arm2Perp.x * armWidth / 2,\n y: arm2End.y + arm2Perp.y * armWidth / 2\n };\n const arm2InnerCorner = {\n x: arm2End.x - arm2Perp.x * armWidth / 2,\n y: arm2End.y - arm2Perp.y * armWidth / 2\n };\n\n // Inner cutout point\n const innerTip = {\n x: 0,\n y: -cutoutDepth\n };\n\n // Build path\n const pathParts: string[] = [];\n\n // Start at outer tip\n if (clampedTipRoundness > 0) {\n const roundAmount = clampedTipRoundness * armWidth * 0.3;\n const tipStart = {\n x: tip.x + arm1Perp.x * roundAmount,\n y: tip.y + arm1Perp.y * roundAmount - roundAmount * 0.5\n };\n pathParts.push(`M ${tipStart.x.toFixed(2)},${tipStart.y.toFixed(2)}`);\n const tipEnd = {\n x: tip.x + arm2Perp.x * roundAmount,\n y: tip.y + arm2Perp.y * roundAmount - roundAmount * 0.5\n };\n pathParts.push(`Q ${tip.x.toFixed(2)},${tip.y.toFixed(2)} ${tipEnd.x.toFixed(2)},${tipEnd.y.toFixed(2)}`);\n } else {\n pathParts.push(`M ${tip.x.toFixed(2)},${tip.y.toFixed(2)}`);\n }\n\n // Outer edge to arm2 end\n pathParts.push(`L ${arm2OuterCorner.x.toFixed(2)},${arm2OuterCorner.y.toFixed(2)}`);\n\n // Top of arm2\n pathParts.push(`L ${arm2InnerCorner.x.toFixed(2)},${arm2InnerCorner.y.toFixed(2)}`);\n\n // Inner edge back to center (the cutout) - only if taper < 0.9\n if (cutoutDepth > 5 && clampedTaper < 0.9) {\n pathParts.push(`L ${innerTip.x.toFixed(2)},${innerTip.y.toFixed(2)}`);\n }\n\n // Inner edge to arm1\n pathParts.push(`L ${arm1InnerCorner.x.toFixed(2)},${arm1InnerCorner.y.toFixed(2)}`);\n\n // Top of arm1\n pathParts.push(`L ${arm1OuterCorner.x.toFixed(2)},${arm1OuterCorner.y.toFixed(2)}`);\n\n // Close back to tip\n pathParts.push('Z');\n\n let path = pathParts.join(' ');\n\n // Calculate bounds\n const allPoints = [tip, arm1End, arm2End, arm1OuterCorner, arm1InnerCorner, arm2OuterCorner, arm2InnerCorner];\n if (cutoutDepth > 5 && clampedTaper < 0.9) {\n allPoints.push(innerTip);\n }\n let minX = Math.min(...allPoints.map(p => p.x));\n let maxX = Math.max(...allPoints.map(p => p.x));\n let minY = Math.min(...allPoints.map(p => p.y));\n let maxY = Math.max(...allPoints.map(p => p.y));\n\n const padding = armWidth * 0.2;\n minX -= padding;\n maxX += padding;\n minY -= padding;\n maxY += padding;\n\n const width = maxX - minX;\n const height = maxY - minY;\n\n path = normalizePath(path, minX, minY);\n\n if (rotation !== 0) {\n path = rotatePath(path, rotation, width, height);\n }\n\n return addSvgFields({ path, width, height, centerX: width / 2, centerY: height / 2 });\n}\n\n// ============================================================================\n// Helper types and functions\n// ============================================================================\n\ninterface Point {\n x: number;\n y: number;\n}\n\nfunction generateArmSpine(\n start: Point,\n end: Point,\n direction: Point,\n length: number,\n curvature: number,\n numSamples: number\n): Point[] {\n const points: Point[] = [];\n const perpDir = { x: direction.y, y: -direction.x };\n const bowAmount = length * 0.4 * curvature;\n const midPoint = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 };\n const controlPoint = {\n x: midPoint.x + perpDir.x * bowAmount,\n y: midPoint.y + perpDir.y * bowAmount\n };\n\n for (let i = 0; i <= numSamples; i++) {\n const t = i / numSamples;\n if (curvature < 0.1) {\n points.push({\n x: start.x + (end.x - start.x) * t,\n y: start.y + (end.y - start.y) * t,\n });\n } else {\n const mt = 1 - t;\n points.push({\n x: mt * mt * start.x + 2 * mt * t * controlPoint.x + t * t * end.x,\n y: mt * mt * start.y + 2 * mt * t * controlPoint.y + t * t * end.y,\n });\n }\n }\n\n return points;\n}\n\nfunction getSpineTangent(spine: Point[], index: number): Point {\n const prev = spine[Math.max(0, index - 1)];\n const next = spine[Math.min(spine.length - 1, index + 1)];\n const dx = next.x - prev.x;\n const dy = next.y - prev.y;\n const len = Math.sqrt(dx * dx + dy * dy);\n return len > 0 ? { x: dx / len, y: dy / len } : { x: 1, y: 0 };\n}\n\nfunction smoothCurveThroughPoints(points: Point[]): string {\n if (points.length < 2) return '';\n\n const parts: string[] = [];\n const tension = 0.3;\n\n for (let i = 0; i < points.length - 1; i++) {\n const p0 = points[Math.max(0, i - 1)];\n const p1 = points[i];\n const p2 = points[i + 1];\n const p3 = points[Math.min(points.length - 1, i + 2)];\n\n const cp1x = p1.x + (p2.x - p0.x) * tension;\n const cp1y = p1.y + (p2.y - p0.y) * tension;\n const cp2x = p2.x - (p3.x - p1.x) * tension;\n const cp2y = p2.y - (p3.y - p1.y) * tension;\n\n parts.push(`C ${cp1x.toFixed(2)},${cp1y.toFixed(2)} ${cp2x.toFixed(2)},${cp2y.toFixed(2)} ${p2.x.toFixed(2)},${p2.y.toFixed(2)}`);\n }\n\n return parts.join(' ');\n}\n\nfunction normalizePath(path: string, minX: number, minY: number): string {\n return path.replace(\n /([-\\d.]+),([-\\d.]+)/g,\n (_, x, y) => `${(parseFloat(x) - minX).toFixed(2)},${(parseFloat(y) - minY).toFixed(2)}`\n );\n}\n\nfunction rotatePath(path: string, rotation: number, width: number, height: number): string {\n const rad = (rotation * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n const cx = width / 2;\n const cy = height / 2;\n\n return path.replace(\n /([-\\d.]+),([-\\d.]+)/g,\n (_, xStr, yStr) => {\n const x = parseFloat(xStr) - cx;\n const y = parseFloat(yStr) - cy;\n const rx = x * cos - y * sin + cx;\n const ry = x * sin + y * cos + cy;\n return `${rx.toFixed(2)},${ry.toFixed(2)}`;\n }\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,39 @@
1
+ interface DingbatResult {
2
+ path: string;
3
+ width: number;
4
+ height: number;
5
+ centerX: number;
6
+ centerY: number;
7
+ viewBox: string;
8
+ svg: string;
9
+ }
10
+ type GeneratorResult = DingbatResult;
11
+
12
+ type BoomerangStyle = 'classic' | 'crescent' | 'horseshoe' | 'chevron';
13
+ interface BoomerangParams {
14
+ style?: BoomerangStyle;
15
+ armLength: number;
16
+ bendAngle: number;
17
+ armCurvature: number;
18
+ bendSharpness: number;
19
+ thickness: number;
20
+ taper: number;
21
+ tipRoundness: number;
22
+ armBalance?: number;
23
+ thicknessBalance?: number;
24
+ rotation?: number;
25
+ }
26
+ /**
27
+ * Boomerang function with preset methods attached.
28
+ */
29
+ interface BoomerangFunction {
30
+ (params: BoomerangParams): GeneratorResult;
31
+ crescent: (overrides?: Partial<BoomerangParams>) => GeneratorResult;
32
+ fatMoon: (overrides?: Partial<BoomerangParams>) => GeneratorResult;
33
+ sharpChevron: (overrides?: Partial<BoomerangParams>) => GeneratorResult;
34
+ thinArc: (overrides?: Partial<BoomerangParams>) => GeneratorResult;
35
+ horseshoe: (overrides?: Partial<BoomerangParams>) => GeneratorResult;
36
+ }
37
+ declare const boomerang: BoomerangFunction;
38
+
39
+ export { type BoomerangParams, type BoomerangStyle, type DingbatResult, type GeneratorResult, boomerang };
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import {
2
+ boomerang
3
+ } from "./chunk-IAFZ3FNR.js";
4
+ export {
5
+ boomerang
6
+ };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,29 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { CSSProperties } from 'react';
3
+ import { DingbatResult, BoomerangParams } from '../index.js';
4
+
5
+ interface DingbatProps {
6
+ /** Shape result from a dingbatch generator */
7
+ shape: DingbatResult;
8
+ /** Fill color (default: currentColor) */
9
+ fill?: string;
10
+ /** Stroke color (default: none) */
11
+ stroke?: string;
12
+ /** Stroke width (default: 0) */
13
+ strokeWidth?: number;
14
+ /** CSS class name */
15
+ className?: string;
16
+ /** Inline styles */
17
+ style?: CSSProperties;
18
+ }
19
+ declare function Dingbat({ shape, fill, stroke, strokeWidth, className, style, }: DingbatProps): react_jsx_runtime.JSX.Element;
20
+
21
+ type PresetName = 'crescent' | 'fatMoon' | 'sharpChevron' | 'thinArc' | 'horseshoe';
22
+ interface BoomerangProps extends Omit<DingbatProps, 'shape'> {
23
+ preset?: PresetName;
24
+ params?: BoomerangParams;
25
+ rotation?: number;
26
+ }
27
+ declare function Boomerang({ preset, params, rotation, ...rest }: BoomerangProps): react_jsx_runtime.JSX.Element;
28
+
29
+ export { Boomerang, type BoomerangProps, Dingbat, type DingbatProps };
@@ -0,0 +1,58 @@
1
+ import {
2
+ boomerang
3
+ } from "../chunk-IAFZ3FNR.js";
4
+
5
+ // react/Dingbat.tsx
6
+ import { jsx } from "react/jsx-runtime";
7
+ function Dingbat({
8
+ shape,
9
+ fill = "currentColor",
10
+ stroke = "none",
11
+ strokeWidth = 0,
12
+ className,
13
+ style
14
+ }) {
15
+ return /* @__PURE__ */ jsx(
16
+ "svg",
17
+ {
18
+ viewBox: shape.viewBox,
19
+ className,
20
+ style,
21
+ xmlns: "http://www.w3.org/2000/svg",
22
+ children: /* @__PURE__ */ jsx(
23
+ "path",
24
+ {
25
+ d: shape.path,
26
+ fill,
27
+ stroke,
28
+ strokeWidth
29
+ }
30
+ )
31
+ }
32
+ );
33
+ }
34
+
35
+ // react/components/Boomerang.tsx
36
+ import { jsx as jsx2 } from "react/jsx-runtime";
37
+ function Boomerang({
38
+ preset,
39
+ params,
40
+ rotation,
41
+ ...rest
42
+ }) {
43
+ const overrides = rotation !== void 0 ? { rotation } : {};
44
+ let shape;
45
+ if (preset) {
46
+ shape = boomerang[preset](overrides);
47
+ } else if (params) {
48
+ shape = boomerang({ ...params, ...overrides });
49
+ } else {
50
+ shape = boomerang.crescent(overrides);
51
+ }
52
+ return /* @__PURE__ */ jsx2(Dingbat, { shape, ...rest });
53
+ }
54
+ export {
55
+ Boomerang,
56
+ Dingbat
57
+ };
58
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../react/Dingbat.tsx","../../react/components/Boomerang.tsx"],"sourcesContent":["// react/Dingbat.tsx\n\nimport type { CSSProperties } from 'react';\nimport type { DingbatResult } from '../src/generators/types';\n\nexport interface DingbatProps {\n /** Shape result from a dingbatch generator */\n shape: DingbatResult;\n /** Fill color (default: currentColor) */\n fill?: string;\n /** Stroke color (default: none) */\n stroke?: string;\n /** Stroke width (default: 0) */\n strokeWidth?: number;\n /** CSS class name */\n className?: string;\n /** Inline styles */\n style?: CSSProperties;\n}\n\nexport function Dingbat({\n shape,\n fill = 'currentColor',\n stroke = 'none',\n strokeWidth = 0,\n className,\n style,\n}: DingbatProps) {\n return (\n <svg\n viewBox={shape.viewBox}\n className={className}\n style={style}\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d={shape.path}\n fill={fill}\n stroke={stroke}\n strokeWidth={strokeWidth}\n />\n </svg>\n );\n}\n","// react/components/Boomerang.tsx\n\nimport { boomerang, BoomerangParams } from '../../src';\nimport { Dingbat, DingbatProps } from '../Dingbat';\n\ntype PresetName = 'crescent' | 'fatMoon' | 'sharpChevron' | 'thinArc' | 'horseshoe';\n\nexport interface BoomerangProps extends Omit<DingbatProps, 'shape'> {\n preset?: PresetName;\n params?: BoomerangParams;\n rotation?: number;\n}\n\nexport function Boomerang({\n preset,\n params,\n rotation,\n ...rest\n}: BoomerangProps) {\n const overrides = rotation !== undefined ? { rotation } : {};\n\n let shape;\n if (preset) {\n shape = boomerang[preset](overrides);\n } else if (params) {\n shape = boomerang({ ...params, ...overrides });\n } else {\n shape = boomerang.crescent(overrides);\n }\n\n return <Dingbat shape={shape} {...rest} />;\n}\n"],"mappings":";;;;;AAmCM;AAfC,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA,OAAO;AAAA,EACP,SAAS;AAAA,EACT,cAAc;AAAA,EACd;AAAA,EACA;AACF,GAAiB;AACf,SACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAS,MAAM;AAAA,MACf;AAAA,MACA;AAAA,MACA,OAAM;AAAA,MAEN;AAAA,QAAC;AAAA;AAAA,UACC,GAAG,MAAM;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA;AAAA,MACF;AAAA;AAAA,EACF;AAEJ;;;ACbS,gBAAAA,YAAA;AAjBF,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAmB;AACjB,QAAM,YAAY,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAE3D,MAAI;AACJ,MAAI,QAAQ;AACV,YAAQ,UAAU,MAAM,EAAE,SAAS;AAAA,EACrC,WAAW,QAAQ;AACjB,YAAQ,UAAU,EAAE,GAAG,QAAQ,GAAG,UAAU,CAAC;AAAA,EAC/C,OAAO;AACL,YAAQ,UAAU,SAAS,SAAS;AAAA,EACtC;AAEA,SAAO,gBAAAA,KAAC,WAAQ,OAAe,GAAG,MAAM;AAC1C;","names":["jsx"]}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "dingbatch",
3
+ "version": "0.1.0",
4
+ "description": "Parametric MCM (Mid-Century Modern) dingbat shapes as SVGs",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./react": {
14
+ "import": "./dist/react/index.js",
15
+ "types": "./dist/react/index.d.ts"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest",
25
+ "prepublishOnly": "npm run build && npm run test"
26
+ },
27
+ "keywords": [
28
+ "svg",
29
+ "shapes",
30
+ "mcm",
31
+ "mid-century-modern",
32
+ "dingbat",
33
+ "react",
34
+ "parametric"
35
+ ],
36
+ "author": "Evan Navarro",
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/EvanCNavarro/dingbatch.git"
41
+ },
42
+ "devDependencies": {
43
+ "@testing-library/dom": "^10.4.0",
44
+ "@testing-library/react": "^16.0.0",
45
+ "@types/react": "^18.2.0",
46
+ "@types/react-dom": "^18.2.0",
47
+ "@vitejs/plugin-react": "^4.0.0",
48
+ "jsdom": "^24.0.0",
49
+ "react": "^18.2.0",
50
+ "react-dom": "^18.2.0",
51
+ "tsup": "^8.0.0",
52
+ "typescript": "^5.0.0",
53
+ "vitest": "^1.0.0"
54
+ },
55
+ "peerDependencies": {
56
+ "react": ">=18.0.0",
57
+ "react-dom": ">=18.0.0"
58
+ },
59
+ "peerDependenciesMeta": {
60
+ "react": {
61
+ "optional": true
62
+ },
63
+ "react-dom": {
64
+ "optional": true
65
+ }
66
+ }
67
+ }