@aznabee/freehand-ui 0.1.3 → 0.1.4

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
@@ -1,96 +1,54 @@
1
- # freehand-ui
1
+ ![freehand-ui - make your UI delightfully human](docs/images/hero.png)
2
2
 
3
- Wrap any existing web element with a beautiful, responsive, hand-drawn doodle layer.
4
-
5
- `freehand-ui` adds a subtle SVG overlay — thin white pen strokes, handwritten notes, arrows, and sparse decorations — without changing your HTML layout or intercepting pointer events.
6
-
7
- ## Install
8
-
9
- ```bash
10
- npm install @aznabee/freehand-ui
11
- ```
12
-
13
- ## Basic usage
3
+ # Freehand-ui
14
4
 
15
- ```javascript
16
- import { doodle } from "@aznabee/freehand-ui";
5
+ Wrap any existing web element with a beautiful, responsive, hand-drawn doodle layer.
17
6
 
18
- doodle(document.querySelector(".card"));
19
- // or
20
- doodle(".card");
21
- ```
7
+ ## What it does
22
8
 
23
- This immediately draws a hand-drawn border around the element and keeps it aligned as the page resizes, scrolls, or reflows.
9
+ `freehand-ui` adds a subtle SVG overlay on top of your UI - thin pen strokes, handwritten notes, arrows, and small decorations. It does **not** change your HTML layout or block clicks (`pointer-events: none`).
24
10
 
25
- ## React / Next.js
11
+ **Good for:** call-to-action buttons, feature cards, onboarding hints, playful marketing UI.
26
12
 
27
- ```jsx
28
- import Doodle from "@aznabee/freehand-ui/react";
13
+ | Feature | One-liner |
14
+ | --------------- | ------------------------------------------------------------------- |
15
+ | **Border** | Hand-drawn frame that follows the element's shape and border-radius |
16
+ | **Notes** | Caveat handwriting beside the element |
17
+ | **Arrows** | Curved, straight, dotted, or looped pointers into the element |
18
+ | **Decorations** | Corner accents, stars, hearts, and more |
19
+ | **Responsive** | Stays aligned on resize, scroll, and reflow |
29
20
 
30
- <Doodle note="start chat" strokeWidth={2}>
31
- <Button />
32
- </Doodle>;
33
- ```
21
+ **Live on [Aznabee.com](https://aznabee.com)** - see it in production on the real product.
34
22
 
35
- Every option below works as a prop. The component ships `"use client"`, so it
36
- drops straight into the Next.js App Router without a wrapper of your own, and
37
- it renders on the server without warnings.
23
+ ## ![Basic hand-drawn border around a card](docs/images/basic.png)
38
24
 
39
- The doodle is drawn around **the wrapped component as a whole** — children are
40
- never decorated individually. To decorate descendants instead, opt in with
41
- `childSelector`:
25
+ ## Install
42
26
 
43
- ```jsx
44
- <Doodle childSelector=".card">
45
- {items.map((item) => <Card key={item.id} className="card" {...item} />)}
46
- </Doodle>
27
+ ```bash
28
+ npm install @aznabee/freehand-ui
47
29
  ```
48
30
 
49
- ### Wrapper element
50
-
51
- `<Doodle>` renders a `<span style="display:inline-flex">` that shrink-wraps its
52
- child, so the frame hugs your component instead of stretching to the full width
53
- of its container. `inline-flex` specifically: an `inline-block` wrapper adds
54
- baseline leading beneath an inline-level child like a `<button>`, which would
55
- leave the frame visibly taller than the button.
56
-
57
- Override it when you need different layout — any extra props go to the DOM node:
58
-
59
- ```jsx
60
- <Doodle as="div" style={{ display: "block" }} className="w-full" note="hi">
61
- <Card />
62
- </Doodle>
63
- ```
31
+ ---
64
32
 
65
- Border radius is picked up automatically: if the wrapper has none of its own,
66
- it inherits the radius of the child it hugs, so a wrapped pill button is still
67
- drawn as a pill.
33
+ ## Vanilla JavaScript
68
34
 
69
- ### Hook
35
+ Import from the main entry point and call `doodle()` on any element or CSS selector.
70
36
 
71
- For an element you already hold a ref to, skip the wrapper:
37
+ ### Quick start
72
38
 
73
- ```jsx
74
- import { useDoodle } from "@aznabee/freehand-ui/react";
39
+ ```javascript
40
+ import { doodle } from "@aznabee/freehand-ui";
75
41
 
76
- function Card() {
77
- const ref = useRef(null);
78
- useDoodle(ref, { note: "new", decorations: true });
79
- return <div ref={ref}>…</div>;
80
- }
42
+ doodle(document.querySelector(".card"));
43
+ // or
44
+ doodle(".card");
81
45
  ```
82
46
 
83
- ### Notes
47
+ This draws a hand-drawn border around the element and keeps it aligned as the page resizes, scrolls, or reflows.
84
48
 
85
- - Inline object props (`note={{ text: "hi" }}`) are compared by value, so a
86
- re-render does not tear down and redraw the overlay with a new random seed.
87
- - The handwriting font is fetched automatically — see [Handwriting
88
- font](#handwriting-font) to use `next/font` or self-host instead.
89
- - `disabled` skips drawing entirely — useful behind a reduced-motion check or a
90
- feature flag.
91
- - TypeScript definitions ship with the package for both entry points.
49
+ ### Configuration
92
50
 
93
- ## Configuration
51
+ Pass a second argument to `doodle()`:
94
52
 
95
53
  ```javascript
96
54
  doodle(".card", {
@@ -98,75 +56,54 @@ doodle(".card", {
98
56
  color: "#ffffff",
99
57
  strokeWidth: 1.5,
100
58
  roughness: 1.5,
101
- padding: 0, // gap between the element's edge and the frame
102
- radius: 20, // optional override; auto-detected from CSS when omitted
59
+ padding: 0, // gap between the element's edge and the frame
60
+ radius: 20, // optional override; auto-detected from CSS when omitted
103
61
  opacity: 0.9,
104
- addBreaks: false, // lift the pen at random points around the outline
105
- fontFamily: null, // override the handwriting stack
62
+ addBreaks: false, // lift the pen at random points around the outline
63
+ fontFamily: null, // override the handwriting stack
106
64
  autoLoadFont: true,
107
65
  });
108
66
  ```
109
67
 
110
- The frame traces the element's own edge by default. Raise `padding` to stand it
111
- off — `padding: 8` leaves a comfortable margin around a card.
68
+ The frame traces the element's own edge by default. Raise `padding` to stand it off - `padding: 8` leaves a comfortable margin around a card.
112
69
 
113
- ## Handwriting font
70
+ ### Handwriting font
114
71
 
115
- Notes are set in [Caveat](https://fonts.google.com/specimen/Caveat). The library
116
- loads it for you the first time a note is drawn, so the handwriting looks right
117
- in a plain app with no font setup of its own — without it the text falls back to
118
- the generic `cursive` family, which on macOS is a calligraphic serif rather than
119
- anything handwritten.
72
+ Notes are set in [Caveat](https://fonts.google.com/specimen/Caveat). The library loads it the first time a note is drawn, so handwriting looks right in a plain app with no font setup. Without it, text falls back to the generic `cursive` family (on macOS that's a calligraphic serif, not handwriting).
120
73
 
121
- It skips the request when the page already provides the family, and only ever
122
- requests it once. To take over:
74
+ It skips the request when the page already provides the family, and only ever requests it once. To take over:
123
75
 
124
76
  ```javascript
125
77
  // self-hosted, or already loaded elsewhere on the page
126
78
  doodle(".card", { note: "hi", autoLoadFont: false });
127
79
 
128
- // your own family — a next/font CSS variable, for instance
80
+ // your own family
129
81
  doodle(".card", { note: "hi", fontFamily: "var(--font-caveat)" });
130
82
  ```
131
83
 
132
- ```jsx
133
- import { Caveat } from "next/font/google";
134
- const caveat = Caveat({ subsets: ["latin"], variable: "--font-caveat" });
84
+ Setting `autoLoadFont: false` without a `fontFamily` falls back to system handwriting faces (Bradley Hand, Segoe Script, Comic Sans MS). Notes are re-measured when a font finishes loading, so the underline always matches the final glyph widths.
135
85
 
136
- <Doodle note="start chat" fontFamily="var(--font-caveat)" autoLoadFont={false}>
137
- <Button />
138
- </Doodle>;
139
- ```
86
+ ### Broken outlines
140
87
 
141
- Setting `autoLoadFont: false` without a `fontFamily` falls back to whatever
142
- handwriting faces the system has (Bradley Hand, Segoe Script, Comic Sans MS).
143
- Notes are re-measured when a font finishes loading, so the underline always
144
- matches the final glyph widths.
145
-
146
- ## Broken outlines
147
-
148
- `addBreaks` cuts randomly sized gaps into the border, so it reads as a few
149
- confident dashes rather than one closed loop:
88
+ `addBreaks` cuts randomly sized gaps into the border so it reads as a few confident dashes rather than one closed loop:
150
89
 
151
90
  ```javascript
152
91
  doodle(".cta", { addBreaks: true });
153
92
 
154
- doodle(".cta", { addBreaks: 5 }); // five gaps
93
+ doodle(".cta", { addBreaks: 5 }); // five gaps
155
94
 
156
95
  doodle(".cta", {
157
96
  addBreaks: {
158
- count: 4, // omit for 2–5 gaps
159
- min: 6, // shortest gap, px
160
- max: 30, // longest gap, px
97
+ count: 4, // omit for 2–5 gaps
98
+ min: 6, // shortest gap, px
99
+ max: 30, // longest gap, px
161
100
  },
162
101
  });
163
102
  ```
164
103
 
165
- Gaps are spread one per section of the perimeter so they never clump, and their
166
- total is capped at a third of the outline so the frame still reads as a frame.
167
- On small elements the gaps shrink to fit.
104
+ Gaps are spread one per section of the perimeter so they never clump, and their total is capped at a third of the outline so the frame still reads as a frame. On small elements the gaps shrink to fit.
168
105
 
169
- ## Handwritten annotations
106
+ ### Handwritten annotations
170
107
 
171
108
  ```javascript
172
109
  doodle(".cta", {
@@ -178,25 +115,17 @@ doodle(".cta", {
178
115
  });
179
116
  ```
180
117
 
181
- Positions: `top`, `top-right`, `right`, `bottom-right`, `bottom`, `bottom-left`, `left`, `top-left`. Same names apply to `arrow.from`/`arrow.to`.
118
+ **Positions:** `top`, `top-right`, `right`, `bottom-right`, `bottom`, `bottom-left`, `left`, `top-left`. Same names apply to `arrow.from` / `arrow.to`.
182
119
 
183
- Notes are measured from their real glyph metrics and laid out so they never sit
184
- on top of the element. The underline is drawn to the measured text width; set
185
- `underline: false` for plain handwriting.
120
+ Notes are measured from real glyph metrics and laid out so they never sit on top of the element. The underline is drawn to the measured text width; set `underline: false` for plain handwriting.
186
121
 
187
- **The side you ask for is the side you get.** When a note runs past the edge of
188
- the viewport it slides horizontally by exactly the amount it overhangs, so a
189
- narrowing viewport walks it gradually inward instead of snapping it to the
190
- opposite side. The only limit is the element itself: a note level with it stops
191
- once they meet, rather than sliding across it.
122
+ ### Placement behavior
192
123
 
193
- Vertical placement is fixed relative to the element and does not react to
194
- scrolling, so a note stays pinned to what it annotates instead of crawling back
195
- into view as the page moves. A note beside an element that is itself jammed
196
- against the viewport edge has nowhere to slide, so it may sit partly off screen
197
- — use a different `position` or an `offset` there.
124
+ - **The side you ask for is the side you get.** When a note runs past the viewport edge it slides horizontally by exactly the amount it overhangs, so a narrowing viewport walks it gradually inward instead of snapping to the opposite side.
125
+ - **Vertical placement is fixed** relative to the element and does not react to scrolling - a note stays pinned to what it annotates.
126
+ - If an element is jammed against the viewport edge, a note may sit partly off screen - use a different `position` or an `offset` there.
198
127
 
199
- Nudge a note off its computed spot with `offset`:
128
+ Nudge a note with `offset`:
200
129
 
201
130
  ```javascript
202
131
  doodle(".cta", {
@@ -204,12 +133,9 @@ doodle(".cta", {
204
133
  });
205
134
  ```
206
135
 
207
- The offset moves the note from the spot `position` chose, and staying in view is
208
- judged from there — so a large offset slides back into the viewport rather than
209
- carrying the note off screen. Vertical offsets are always applied verbatim,
210
- since vertical placement never reacts to the viewport.
136
+ The offset moves the note from the spot `position` chose. A large horizontal offset slides back into the viewport rather than carrying the note off screen.
211
137
 
212
- ## Arrows
138
+ ### Arrows
213
139
 
214
140
  ```javascript
215
141
  doodle(".cta", { arrow: true });
@@ -218,17 +144,20 @@ doodle(".cta", {
218
144
  arrow: {
219
145
  from: "top-right", // note | any position name
220
146
  to: "center",
221
- style: "curved", // curved | straight | dotted | looped
147
+ style: "curved", // curved | straight | dotted | looped
222
148
  },
223
149
  });
224
150
  ```
225
151
 
226
- `looped` ties a curl into the middle of the sweep — the hand-drawn flourish that
227
- doubles back on itself before reaching the tip.
152
+ ![Arrow styles - curved, straight, dotted, and looped](docs/images/arrows.png)
153
+
154
+ **Styles:** `curved` (default), `straight`, `dotted`, `looped` - `looped` adds a flourish that doubles back before reaching the tip.
155
+
156
+ **`from`** decides where the arrow leaves:
228
157
 
229
- `from` decides where the arrow leaves. **With a note it names a side of the
230
- note** where the pen lifts off the handwriting and the arrow then travels to
231
- the element wherever that is:
158
+ - **With a note** - names a side of the note (where the pen lifts off the handwriting), then travels to the element.
159
+ - **`"note"`** (default) - picks the side of the note facing the element.
160
+ - **Without a note** - `from` names a side of the element itself.
232
161
 
233
162
  ```javascript
234
163
  doodle(".cta", {
@@ -237,62 +166,43 @@ doodle(".cta", {
237
166
  });
238
167
  ```
239
168
 
240
- `"note"` is the default and picks the side of the note facing the element. A
241
- callout always departs from its note, so moving the note with `offset` takes the
242
- arrow with it rather than leaving it stranded by the element.
169
+ **`to`:** `"edge"` (default) lands the tip just outside the frame; `"center"` points into the element.
243
170
 
244
- Without a note there is no handwriting to leave from, so `from` falls back to
245
- naming a side of the element itself.
246
-
247
- `to: "edge"` (the default) lands the tip just *outside* the frame; `to: "center"`
248
- is the one setting that deliberately points into the element.
249
-
250
- ## Decorations
171
+ ### Decorations
251
172
 
252
173
  ```javascript
253
174
  doodle(".card", { decorations: true });
254
175
 
255
176
  doodle(".card", {
256
177
  decorations: {
257
- count: 2, // border marks, hard-capped at 2
258
- style: "corners", // corners | sides omit to pick automatically
178
+ count: 2, // border marks, hard-capped at 2
179
+ style: "corners", // corners | sides - omit to pick automatically
259
180
  types: ["arcs", "twinkle", "heart"],
260
181
  },
261
182
  });
262
183
  ```
263
184
 
264
- A frame stays readable with **at most two marks around it**, so `count` is
265
- capped at 2 and the default composition is deliberately fixed:
185
+ ![Decoration types - corner accents, stars, handwriting marks, and more](docs/images/decorations.png)
266
186
 
267
- - **one corner accent** `arcs` or `emphasis` hugging a corner, aimed outward
268
- - **one corner star** — `twinkle` or `star` — on the corner farthest from it,
269
- and sitting noticeably further off the border than the accent
270
- - **one handwriting accent** — `heart` or `sparkle` — beside the note, not the
271
- component
187
+ A frame stays readable with **at most two marks around it**. Default composition:
272
188
 
273
- Corner marks follow the *rounded* corner rather than the bounding box, so they
274
- hug a pill's cap as closely as a card's corner, and any mark too large for the
275
- gap it landed in is pushed further out. Everything keeps off the element, off
276
- the note, and off the arrow's sweep.
189
+ - **one corner accent** - `arcs` or `emphasis` - hugging a corner, aimed outward
190
+ - **one corner star** - `twinkle` or `star` - on the corner farthest from it
191
+ - **one handwriting accent** - `heart` or `sparkle` - beside the note, not the component
192
+
193
+ Corner marks follow the _rounded_ corner rather than the bounding box. Marks too large for their gap are pushed further out.
277
194
 
278
195
  ### Handwriting accents
279
196
 
280
- `heart` and `sparkle` belong to text: they are drawn at the far end of the
281
- note, on the side away from the element. With no `note` on the element they are
282
- skipped rather than moved to the border.
197
+ `heart` and `sparkle` are drawn at the far end of the note, on the side away from the element. With no `note` they are skipped rather than moved to the border.
283
198
 
284
199
  ### Small components
285
200
 
286
- Chips and icon buttons have no corner worth pointing at, so they get a mirrored
287
- pair of two-stroke `emphasis` marks — one either side — instead of corner
288
- marks. This is chosen automatically when the element is under 48px on its short
289
- side or under 130px wide; `style: "sides"` forces it at any size and
290
- `style: "corners"` opts out.
201
+ Chips and icon buttons get a mirrored pair of two-stroke `emphasis` marks - one either side - instead of corner marks. This is automatic when the element is under 48px on its short side or under 130px wide. `style: "sides"` forces it at any size; `style: "corners"` opts out.
291
202
 
292
- The remaining types `star`, `smiley`, `dots`, `stroke`, `steam` are still
293
- available and fill a corner slot when no accent or star was requested.
203
+ Other types - `star`, `smiley`, `dots`, `stroke`, `steam` - fill a corner slot when no accent or star was requested.
294
204
 
295
- ## Child selector mode
205
+ ### Child selector mode
296
206
 
297
207
  Decorate multiple children from a single parent overlay:
298
208
 
@@ -302,16 +212,113 @@ doodle("#hero", {
302
212
  });
303
213
  ```
304
214
 
305
- ## Lifecycle
215
+ ### Lifecycle
306
216
 
307
217
  ```javascript
308
218
  const instance = doodle(".card");
309
219
 
310
- instance.update(); // recalculate geometry and redraw
220
+ instance.update(); // recalculate geometry and redraw
311
221
  instance.destroy(); // remove overlay and disconnect observers
312
222
  ```
313
223
 
314
- Calling `doodle()` twice on the same element replaces the previous overlay no duplicates.
224
+ Calling `doodle()` twice on the same element replaces the previous overlay - no duplicates.
225
+
226
+ ---
227
+
228
+ ## React / Next.js
229
+
230
+ Import from `@aznabee/freehand-ui/react`. Every option in the [Vanilla JavaScript](#vanilla-javascript) section works as a prop.
231
+
232
+ ### Component
233
+
234
+ ```jsx
235
+ import Doodle from "@aznabee/freehand-ui/react";
236
+
237
+ <Doodle note="start chat" strokeWidth={2}>
238
+ <Button />
239
+ </Doodle>;
240
+ ```
241
+
242
+ Ships `"use client"` - drops into the Next.js App Router without a wrapper, and renders on the server without warnings.
243
+
244
+ The doodle is drawn around **the wrapped component as a whole** - children are never decorated individually. To decorate descendants instead, opt in with `childSelector`:
245
+
246
+ ```jsx
247
+ <Doodle childSelector=".card">
248
+ {items.map((item) => (
249
+ <Card key={item.id} className="card" {...item} />
250
+ ))}
251
+ </Doodle>
252
+ ```
253
+
254
+ ### Configuration
255
+
256
+ Same options as `doodle()`, passed as props:
257
+
258
+ ```jsx
259
+ <Doodle
260
+ border
261
+ color="#ffffff"
262
+ strokeWidth={1.5}
263
+ roughness={1.5}
264
+ padding={0}
265
+ radius={20}
266
+ opacity={0.9}
267
+ addBreaks={false}
268
+ autoLoadFont
269
+ >
270
+ <Card />
271
+ </Doodle>
272
+ ```
273
+
274
+ ### Wrapper element
275
+
276
+ `<Doodle>` renders a `<span style="display:inline-flex">` that shrink-wraps its child, so the frame hugs your component instead of stretching to the full width of its container. `inline-flex` specifically: an `inline-block` wrapper adds baseline leading beneath an inline-level child like a `<button>`, which would leave the frame visibly taller than the button.
277
+
278
+ Override when you need different layout - extra props go to the DOM node:
279
+
280
+ ```jsx
281
+ <Doodle as="div" style={{ display: "block" }} className="w-full" note="hi">
282
+ <Card />
283
+ </Doodle>
284
+ ```
285
+
286
+ Border radius is picked up automatically: if the wrapper has none of its own, it inherits the radius of the child it hugs, so a wrapped pill button is still drawn as a pill.
287
+
288
+ ### Hook
289
+
290
+ For an element you already hold a ref to, skip the wrapper:
291
+
292
+ ```jsx
293
+ import { useDoodle } from "@aznabee/freehand-ui/react";
294
+
295
+ function Card() {
296
+ const ref = useRef(null);
297
+ useDoodle(ref, { note: "new", decorations: true });
298
+ return <div ref={ref}>…</div>;
299
+ }
300
+ ```
301
+
302
+ ### Handwriting font
303
+
304
+ With `next/font`:
305
+
306
+ ```jsx
307
+ import { Caveat } from "next/font/google";
308
+ const caveat = Caveat({ subsets: ["latin"], variable: "--font-caveat" });
309
+
310
+ <Doodle note="start chat" fontFamily="var(--font-caveat)" autoLoadFont={false}>
311
+ <Button />
312
+ </Doodle>;
313
+ ```
314
+
315
+ ### React notes
316
+
317
+ - Inline object props (`note={{ text: "hi" }}`) are compared by value, so a re-render does not tear down and redraw the overlay with a new random seed.
318
+ - `disabled` skips drawing entirely - useful behind a reduced-motion check or a feature flag.
319
+ - TypeScript definitions ship with the package for both entry points.
320
+
321
+ ---
315
322
 
316
323
  ## Examples
317
324
 
@@ -327,16 +334,22 @@ Then open:
327
334
 
328
335
  - http://localhost:5173/examples/basic/
329
336
  - http://localhost:5173/examples/annotations/
337
+ - http://localhost:5173/examples/arrows/
330
338
  - http://localhost:5173/examples/children/
331
339
  - http://localhost:5173/examples/decorations/
340
+ - http://localhost:5173/examples/react/
341
+
342
+ ---
332
343
 
333
344
  ## Design principles
334
345
 
335
- - **Framework agnostic** plain JavaScript at the core; React is an optional entry point
336
- - **SVG based** hand-drawn paths, not CSS borders
337
- - **Non-invasive** `pointer-events: none`, no layout changes
338
- - **Responsive** `ResizeObserver`, scroll listeners, and `requestAnimationFrame`
339
- - **Lightweight** zero runtime dependencies
346
+ - **Framework agnostic** - plain JavaScript at the core; React is an optional entry point
347
+ - **SVG based** - hand-drawn paths, not CSS borders
348
+ - **Non-invasive** - `pointer-events: none`, no layout changes
349
+ - **Responsive** - `ResizeObserver`, scroll listeners, and `requestAnimationFrame`
350
+ - **Lightweight** - zero runtime dependencies
351
+
352
+ ---
340
353
 
341
354
  ## License
342
355
 
@@ -484,21 +484,14 @@ function isBroken(fraction, breaks) {
484
484
  function createBorderBreaks(perimeter, config, random) {
485
485
  if (!config || perimeter <= 0) return [];
486
486
  const settings = config === true ? {} : config;
487
- const count = Math.max(
488
- 1,
489
- Math.round(settings.count ?? 2 + random() * 3)
490
- );
487
+ const count = Math.max(1, Math.round(settings.count ?? 2 + random() * 3));
491
488
  const min = Math.max(2, settings.min ?? 6);
492
489
  const max = Math.max(min, settings.max ?? 30);
493
490
  const slice = perimeter / count;
494
491
  let budget = perimeter * 0.35;
495
492
  const breaks = [];
496
493
  for (let i = 0; i < count; i++) {
497
- const length = Math.min(
498
- min + random() * (max - min),
499
- slice * 0.7,
500
- budget
501
- );
494
+ const length = Math.min(min + random() * (max - min), slice * 0.7, budget);
502
495
  if (length < 2) break;
503
496
  budget -= length;
504
497
  const start = i * slice + random() * Math.max(0, slice - length);
@@ -459,21 +459,14 @@ function isBroken(fraction, breaks) {
459
459
  function createBorderBreaks(perimeter, config, random) {
460
460
  if (!config || perimeter <= 0) return [];
461
461
  const settings = config === true ? {} : config;
462
- const count = Math.max(
463
- 1,
464
- Math.round(settings.count ?? 2 + random() * 3)
465
- );
462
+ const count = Math.max(1, Math.round(settings.count ?? 2 + random() * 3));
466
463
  const min = Math.max(2, settings.min ?? 6);
467
464
  const max = Math.max(min, settings.max ?? 30);
468
465
  const slice = perimeter / count;
469
466
  let budget = perimeter * 0.35;
470
467
  const breaks = [];
471
468
  for (let i = 0; i < count; i++) {
472
- const length = Math.min(
473
- min + random() * (max - min),
474
- slice * 0.7,
475
- budget
476
- );
469
+ const length = Math.min(min + random() * (max - min), slice * 0.7, budget);
477
470
  if (length < 2) break;
478
471
  budget -= length;
479
472
  const start = i * slice + random() * Math.max(0, slice - length);
package/dist/index.d.ts CHANGED
@@ -24,7 +24,7 @@ export type DecorationType =
24
24
  export interface NoteOptions {
25
25
  text: string;
26
26
  /**
27
- * Which side of the element the note sits on. Always honoured a note that
27
+ * Which side of the element the note sits on. Always honoured - a note that
28
28
  * would run off screen slides horizontally to stay in view rather than
29
29
  * moving to another side.
30
30
  */
@@ -41,7 +41,7 @@ export interface NoteOptions {
41
41
 
42
42
  export interface ArrowOptions {
43
43
  /**
44
- * Where the arrow leaves. With a `note` this names a side of *the note*
44
+ * Where the arrow leaves. With a `note` this names a side of *the note* -
45
45
  * the arrow always departs from the handwriting and travels to the element,
46
46
  * however far an `offset` has moved it. `"note"` (the default) picks the side
47
47
  * facing the element. Without a note it names a side of the element instead.
@@ -88,8 +88,8 @@ export interface DoodleOptions {
88
88
  /** Lift the pen at random points around the outline. */
89
89
  addBreaks?: boolean | number | BreakOptions;
90
90
  /**
91
- * Font stack for handwritten notes. Point this at your own family a
92
- * `next/font` CSS variable, for instance to take over from the built-in one.
91
+ * Font stack for handwritten notes. Point this at your own family - a
92
+ * `next/font` CSS variable, for instance - to take over from the built-in one.
93
93
  */
94
94
  fontFamily?: string | null;
95
95
  /**
@@ -114,5 +114,5 @@ export interface DoodleInstance {
114
114
  */
115
115
  export function doodle(
116
116
  target: string | Element,
117
- options?: DoodleOptions
117
+ options?: DoodleOptions,
118
118
  ): DoodleInstance;
package/dist/react.cjs CHANGED
@@ -489,21 +489,14 @@ function isBroken(fraction, breaks) {
489
489
  function createBorderBreaks(perimeter, config, random) {
490
490
  if (!config || perimeter <= 0) return [];
491
491
  const settings = config === true ? {} : config;
492
- const count = Math.max(
493
- 1,
494
- Math.round(settings.count ?? 2 + random() * 3)
495
- );
492
+ const count = Math.max(1, Math.round(settings.count ?? 2 + random() * 3));
496
493
  const min = Math.max(2, settings.min ?? 6);
497
494
  const max = Math.max(min, settings.max ?? 30);
498
495
  const slice = perimeter / count;
499
496
  let budget = perimeter * 0.35;
500
497
  const breaks = [];
501
498
  for (let i = 0; i < count; i++) {
502
- const length = Math.min(
503
- min + random() * (max - min),
504
- slice * 0.7,
505
- budget
506
- );
499
+ const length = Math.min(min + random() * (max - min), slice * 0.7, budget);
507
500
  if (length < 2) break;
508
501
  budget -= length;
509
502
  const start = i * slice + random() * Math.max(0, slice - length);
@@ -1795,7 +1788,7 @@ var Doodle = (0, import_react.forwardRef)(function Doodle2(props, forwardedRef)
1795
1788
  ref: mergeRefs(innerRef, forwardedRef),
1796
1789
  className,
1797
1790
  // inline-flex shrink-wraps the child so the doodle hugs the component
1798
- // rather than a full-width block. Flex specifically inline-block would
1791
+ // rather than a full-width block. Flex specifically - inline-block would
1799
1792
  // add baseline leading under an inline-level child like a <button>,
1800
1793
  // leaving the frame taller than what it wraps. Override via `style`/`as`.
1801
1794
  style: { display: "inline-flex", ...style }
package/dist/react.d.ts CHANGED
@@ -24,7 +24,7 @@ export interface DoodleOwnProps extends DoodleOptionProps {
24
24
  children?: ReactNode;
25
25
  /** Wrapper element to render. Default `"span"`. */
26
26
  as?: ElementType;
27
- /** Skip drawing entirely useful for reduced-motion or feature flags. */
27
+ /** Skip drawing entirely - useful for reduced-motion or feature flags. */
28
28
  disabled?: boolean;
29
29
  /**
30
30
  * Selector for decorating descendants instead of the wrapper as a whole.
@@ -52,7 +52,7 @@ export type DoodleProps<T extends ElementType = "span"> = DoodleOwnProps &
52
52
  * decorated individually unless you opt in with `childSelector`.
53
53
  */
54
54
  declare const Doodle: <T extends ElementType = "span">(
55
- props: DoodleProps<T>
55
+ props: DoodleProps<T>,
56
56
  ) => JSX.Element;
57
57
 
58
58
  export default Doodle;
@@ -62,5 +62,5 @@ export { Doodle };
62
62
  export function useDoodle(
63
63
  ref: RefObject<Element | null>,
64
64
  options?: DoodleOptions,
65
- enabled?: boolean
65
+ enabled?: boolean,
66
66
  ): void;
package/dist/react.js CHANGED
@@ -472,21 +472,14 @@ function isBroken(fraction, breaks) {
472
472
  function createBorderBreaks(perimeter, config, random) {
473
473
  if (!config || perimeter <= 0) return [];
474
474
  const settings = config === true ? {} : config;
475
- const count = Math.max(
476
- 1,
477
- Math.round(settings.count ?? 2 + random() * 3)
478
- );
475
+ const count = Math.max(1, Math.round(settings.count ?? 2 + random() * 3));
479
476
  const min = Math.max(2, settings.min ?? 6);
480
477
  const max = Math.max(min, settings.max ?? 30);
481
478
  const slice = perimeter / count;
482
479
  let budget = perimeter * 0.35;
483
480
  const breaks = [];
484
481
  for (let i = 0; i < count; i++) {
485
- const length = Math.min(
486
- min + random() * (max - min),
487
- slice * 0.7,
488
- budget
489
- );
482
+ const length = Math.min(min + random() * (max - min), slice * 0.7, budget);
490
483
  if (length < 2) break;
491
484
  budget -= length;
492
485
  const start = i * slice + random() * Math.max(0, slice - length);
@@ -1778,7 +1771,7 @@ var Doodle = forwardRef(function Doodle2(props, forwardedRef) {
1778
1771
  ref: mergeRefs(innerRef, forwardedRef),
1779
1772
  className,
1780
1773
  // inline-flex shrink-wraps the child so the doodle hugs the component
1781
- // rather than a full-width block. Flex specifically inline-block would
1774
+ // rather than a full-width block. Flex specifically - inline-block would
1782
1775
  // add baseline leading under an inline-level child like a <button>,
1783
1776
  // leaving the frame taller than what it wraps. Override via `style`/`as`.
1784
1777
  style: { display: "inline-flex", ...style }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aznabee/freehand-ui",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Wrap any existing web element with a beautiful, responsive, hand-drawn doodle layer",
5
5
  "type": "module",
6
6
  "main": "./dist/freehand-ui.cjs",