@aznabee/freehand-ui 0.1.2 → 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 +212 -157
- package/dist/freehand-ui.cjs +251 -135
- package/dist/freehand-ui.js +251 -135
- package/dist/index.d.ts +22 -6
- package/dist/react.cjs +252 -136
- package/dist/react.d.ts +3 -3
- package/dist/react.js +252 -136
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,96 +1,54 @@
|
|
|
1
|
-
|
|
1
|
+

|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
```
|
|
3
|
+
# Freehand-ui
|
|
12
4
|
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
19
|
-
// or
|
|
20
|
-
doodle(".card");
|
|
21
|
-
```
|
|
7
|
+
## What it does
|
|
22
8
|
|
|
23
|
-
|
|
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
|
-
|
|
11
|
+
**Good for:** call-to-action buttons, feature cards, onboarding hints, playful marketing UI.
|
|
26
12
|
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
31
|
-
<Button />
|
|
32
|
-
</Doodle>;
|
|
33
|
-
```
|
|
21
|
+
**Live on [Aznabee.com](https://aznabee.com)** - see it in production on the real product.
|
|
34
22
|
|
|
35
|
-
|
|
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
|
+
## 
|
|
38
24
|
|
|
39
|
-
|
|
40
|
-
never decorated individually. To decorate descendants instead, opt in with
|
|
41
|
-
`childSelector`:
|
|
25
|
+
## Install
|
|
42
26
|
|
|
43
|
-
```
|
|
44
|
-
|
|
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
|
-
|
|
31
|
+
---
|
|
50
32
|
|
|
51
|
-
|
|
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.
|
|
33
|
+
## Vanilla JavaScript
|
|
56
34
|
|
|
57
|
-
|
|
35
|
+
Import from the main entry point and call `doodle()` on any element or CSS selector.
|
|
58
36
|
|
|
59
|
-
|
|
60
|
-
<Doodle as="div" style={{ display: "block" }} className="w-full" note="hi">
|
|
61
|
-
<Card />
|
|
62
|
-
</Doodle>
|
|
63
|
-
```
|
|
64
|
-
|
|
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.
|
|
68
|
-
|
|
69
|
-
### Hook
|
|
37
|
+
### Quick start
|
|
70
38
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
```jsx
|
|
74
|
-
import { useDoodle } from "@aznabee/freehand-ui/react";
|
|
39
|
+
```javascript
|
|
40
|
+
import { doodle } from "@aznabee/freehand-ui";
|
|
75
41
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return <div ref={ref}>…</div>;
|
|
80
|
-
}
|
|
42
|
+
doodle(document.querySelector(".card"));
|
|
43
|
+
// or
|
|
44
|
+
doodle(".card");
|
|
81
45
|
```
|
|
82
46
|
|
|
83
|
-
|
|
47
|
+
This draws a hand-drawn border around the element and keeps it aligned as the page resizes, scrolls, or reflows.
|
|
84
48
|
|
|
85
|
-
|
|
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
|
-
|
|
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,
|
|
102
|
-
radius: 20,
|
|
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,
|
|
105
|
-
fontFamily: null,
|
|
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
|
-
|
|
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
|
|
80
|
+
// your own family
|
|
129
81
|
doodle(".card", { note: "hi", fontFamily: "var(--font-caveat)" });
|
|
130
82
|
```
|
|
131
83
|
|
|
132
|
-
|
|
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
|
-
|
|
137
|
-
<Button />
|
|
138
|
-
</Doodle>;
|
|
139
|
-
```
|
|
140
|
-
|
|
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
|
|
86
|
+
### Broken outlines
|
|
147
87
|
|
|
148
|
-
`addBreaks` cuts randomly sized gaps into the border
|
|
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 });
|
|
93
|
+
doodle(".cta", { addBreaks: 5 }); // five gaps
|
|
155
94
|
|
|
156
95
|
doodle(".cta", {
|
|
157
96
|
addBreaks: {
|
|
158
|
-
count: 4,
|
|
159
|
-
min: 6,
|
|
160
|
-
max: 30,
|
|
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
|
-
|
|
106
|
+
### Handwritten annotations
|
|
170
107
|
|
|
171
108
|
```javascript
|
|
172
109
|
doodle(".cta", {
|
|
@@ -178,79 +115,94 @@ doodle(".cta", {
|
|
|
178
115
|
});
|
|
179
116
|
```
|
|
180
117
|
|
|
181
|
-
Positions
|
|
118
|
+
**Positions:** `top`, `top-right`, `right`, `bottom-right`, `bottom`, `bottom-left`, `left`, `top-left`. Same names apply to `arrow.from` / `arrow.to`.
|
|
119
|
+
|
|
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.
|
|
121
|
+
|
|
122
|
+
### Placement behavior
|
|
123
|
+
|
|
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.
|
|
127
|
+
|
|
128
|
+
Nudge a note with `offset`:
|
|
182
129
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
130
|
+
```javascript
|
|
131
|
+
doodle(".cta", {
|
|
132
|
+
note: { text: "start chat", position: "top", offset: { x: 40, y: -12 } },
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
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.
|
|
188
137
|
|
|
189
|
-
|
|
138
|
+
### Arrows
|
|
190
139
|
|
|
191
140
|
```javascript
|
|
192
141
|
doodle(".cta", { arrow: true });
|
|
193
142
|
|
|
194
143
|
doodle(".cta", {
|
|
195
144
|
arrow: {
|
|
196
|
-
from: "top-right",
|
|
145
|
+
from: "top-right", // note | any position name
|
|
197
146
|
to: "center",
|
|
198
|
-
style: "curved", // curved | straight | dotted
|
|
147
|
+
style: "curved", // curved | straight | dotted | looped
|
|
199
148
|
},
|
|
200
149
|
});
|
|
201
150
|
```
|
|
202
151
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
152
|
+

|
|
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:
|
|
157
|
+
|
|
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.
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
doodle(".cta", {
|
|
164
|
+
note: { text: "start chat", position: "top-right" },
|
|
165
|
+
arrow: { from: "bottom-left" }, // leaves the note's bottom-left corner
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**`to`:** `"edge"` (default) lands the tip just outside the frame; `"center"` points into the element.
|
|
207
170
|
|
|
208
|
-
|
|
171
|
+
### Decorations
|
|
209
172
|
|
|
210
173
|
```javascript
|
|
211
174
|
doodle(".card", { decorations: true });
|
|
212
175
|
|
|
213
176
|
doodle(".card", {
|
|
214
177
|
decorations: {
|
|
215
|
-
count: 2,
|
|
216
|
-
style: "corners",
|
|
178
|
+
count: 2, // border marks, hard-capped at 2
|
|
179
|
+
style: "corners", // corners | sides - omit to pick automatically
|
|
217
180
|
types: ["arcs", "twinkle", "heart"],
|
|
218
181
|
},
|
|
219
182
|
});
|
|
220
183
|
```
|
|
221
184
|
|
|
222
|
-
|
|
223
|
-
capped at 2 and the default composition is deliberately fixed:
|
|
185
|
+

|
|
224
186
|
|
|
225
|
-
|
|
226
|
-
- **one corner star** — `twinkle` or `star` — on the corner farthest from it,
|
|
227
|
-
and sitting noticeably further off the border than the accent
|
|
228
|
-
- **one handwriting accent** — `heart` or `sparkle` — beside the note, not the
|
|
229
|
-
component
|
|
187
|
+
A frame stays readable with **at most two marks around it**. Default composition:
|
|
230
188
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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.
|
|
235
194
|
|
|
236
195
|
### Handwriting accents
|
|
237
196
|
|
|
238
|
-
`heart` and `sparkle`
|
|
239
|
-
note, on the side away from the element. With no `note` on the element they are
|
|
240
|
-
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.
|
|
241
198
|
|
|
242
199
|
### Small components
|
|
243
200
|
|
|
244
|
-
Chips and icon buttons
|
|
245
|
-
pair of two-stroke `emphasis` marks — one either side — instead of corner
|
|
246
|
-
marks. This is chosen automatically when the element is under 48px on its short
|
|
247
|
-
side or under 130px wide; `style: "sides"` forces it at any size and
|
|
248
|
-
`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.
|
|
249
202
|
|
|
250
|
-
|
|
251
|
-
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.
|
|
252
204
|
|
|
253
|
-
|
|
205
|
+
### Child selector mode
|
|
254
206
|
|
|
255
207
|
Decorate multiple children from a single parent overlay:
|
|
256
208
|
|
|
@@ -260,16 +212,113 @@ doodle("#hero", {
|
|
|
260
212
|
});
|
|
261
213
|
```
|
|
262
214
|
|
|
263
|
-
|
|
215
|
+
### Lifecycle
|
|
264
216
|
|
|
265
217
|
```javascript
|
|
266
218
|
const instance = doodle(".card");
|
|
267
219
|
|
|
268
|
-
instance.update();
|
|
220
|
+
instance.update(); // recalculate geometry and redraw
|
|
269
221
|
instance.destroy(); // remove overlay and disconnect observers
|
|
270
222
|
```
|
|
271
223
|
|
|
272
|
-
Calling `doodle()` twice on the same element replaces the previous overlay
|
|
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
|
+
---
|
|
273
322
|
|
|
274
323
|
## Examples
|
|
275
324
|
|
|
@@ -285,16 +334,22 @@ Then open:
|
|
|
285
334
|
|
|
286
335
|
- http://localhost:5173/examples/basic/
|
|
287
336
|
- http://localhost:5173/examples/annotations/
|
|
337
|
+
- http://localhost:5173/examples/arrows/
|
|
288
338
|
- http://localhost:5173/examples/children/
|
|
289
339
|
- http://localhost:5173/examples/decorations/
|
|
340
|
+
- http://localhost:5173/examples/react/
|
|
341
|
+
|
|
342
|
+
---
|
|
290
343
|
|
|
291
344
|
## Design principles
|
|
292
345
|
|
|
293
|
-
- **Framework agnostic**
|
|
294
|
-
- **SVG based**
|
|
295
|
-
- **Non-invasive**
|
|
296
|
-
- **Responsive**
|
|
297
|
-
- **Lightweight**
|
|
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
|
+
---
|
|
298
353
|
|
|
299
354
|
## License
|
|
300
355
|
|