@aznabee/freehand-ui 0.1.1 → 0.1.2

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
@@ -84,6 +84,8 @@ function Card() {
84
84
 
85
85
  - Inline object props (`note={{ text: "hi" }}`) are compared by value, so a
86
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.
87
89
  - `disabled` skips drawing entirely — useful behind a reduced-motion check or a
88
90
  feature flag.
89
91
  - TypeScript definitions ship with the package for both entry points.
@@ -96,13 +98,51 @@ doodle(".card", {
96
98
  color: "#ffffff",
97
99
  strokeWidth: 1.5,
98
100
  roughness: 1.5,
99
- padding: 8,
100
- radius: 20, // optional override; auto-detected from CSS when omitted
101
+ padding: 0, // gap between the element's edge and the frame
102
+ radius: 20, // optional override; auto-detected from CSS when omitted
101
103
  opacity: 0.9,
102
- addBreaks: false, // lift the pen at random points around the outline
104
+ addBreaks: false, // lift the pen at random points around the outline
105
+ fontFamily: null, // override the handwriting stack
106
+ autoLoadFont: true,
103
107
  });
104
108
  ```
105
109
 
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.
112
+
113
+ ## Handwriting font
114
+
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.
120
+
121
+ It skips the request when the page already provides the family, and only ever
122
+ requests it once. To take over:
123
+
124
+ ```javascript
125
+ // self-hosted, or already loaded elsewhere on the page
126
+ doodle(".card", { note: "hi", autoLoadFont: false });
127
+
128
+ // your own family — a next/font CSS variable, for instance
129
+ doodle(".card", { note: "hi", fontFamily: "var(--font-caveat)" });
130
+ ```
131
+
132
+ ```jsx
133
+ import { Caveat } from "next/font/google";
134
+ const caveat = Caveat({ subsets: ["latin"], variable: "--font-caveat" });
135
+
136
+ <Doodle note="start chat" fontFamily="var(--font-caveat)" autoLoadFont={false}>
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
+
106
146
  ## Broken outlines
107
147
 
108
148
  `addBreaks` cuts randomly sized gaps into the border, so it reads as a few
@@ -29,14 +29,19 @@ var DEFAULT_OPTIONS = {
29
29
  color: "#ffffff",
30
30
  strokeWidth: 1.5,
31
31
  roughness: 1.5,
32
- padding: 8,
32
+ // The frame traces the element's own edge. Raise this to stand it off.
33
+ padding: 0,
33
34
  radius: null,
34
35
  opacity: 0.9,
35
36
  children: null,
36
37
  note: null,
37
38
  arrow: false,
38
39
  decorations: false,
39
- addBreaks: false
40
+ addBreaks: false,
41
+ /** Override the handwriting stack, e.g. a next/font CSS variable. */
42
+ fontFamily: null,
43
+ /** Fetch Caveat when the page has not provided it. */
44
+ autoLoadFont: true
40
45
  };
41
46
  function resolveElement(target) {
42
47
  if (!target) return null;
@@ -262,7 +267,7 @@ function annotationAnchor(position, rect, offset = 12) {
262
267
 
263
268
  // src/renderer.js
264
269
  var SVG_NS = "http://www.w3.org/2000/svg";
265
- var HANDWRITTEN_FONT = '"Caveat", "Kalam", "Patrick Hand", cursive';
270
+ var HANDWRITTEN_FONT = '"Caveat", "Kalam", "Patrick Hand", "Bradley Hand", "Segoe Script", "Comic Sans MS", cursive';
266
271
  var NOTE_FONT_SIZE = 19;
267
272
  function clearSvg(svg) {
268
273
  while (svg.firstChild) {
@@ -741,7 +746,7 @@ function createNoteText(svg, text, style) {
741
746
  textEl.setAttribute("fill", style.color);
742
747
  textEl.setAttribute("opacity", String(style.opacity));
743
748
  textEl.setAttribute("font-size", String(fontSize));
744
- textEl.setAttribute("font-family", HANDWRITTEN_FONT);
749
+ textEl.setAttribute("font-family", style.fontFamily || HANDWRITTEN_FONT);
745
750
  textEl.setAttribute("font-weight", "600");
746
751
  textEl.setAttribute("letter-spacing", "0.4");
747
752
  textEl.setAttribute("text-anchor", "start");
@@ -1314,6 +1319,27 @@ function renderDecorations(svg, rect, decorationOptions, style, seed, context =
1314
1319
  }
1315
1320
  }
1316
1321
 
1322
+ // src/fonts.js
1323
+ var FONT_NAME = "Caveat";
1324
+ var FONT_HREF = "https://fonts.googleapis.com/css2?family=Caveat:wght@400;600&display=swap";
1325
+ var MARKER = "data-freehand-font";
1326
+ var requested = false;
1327
+ function ensureHandwrittenFont() {
1328
+ if (requested) return;
1329
+ if (typeof document === "undefined" || !document.head) return;
1330
+ requested = true;
1331
+ try {
1332
+ if (document.fonts?.check?.(`600 19px "${FONT_NAME}"`)) return;
1333
+ } catch {
1334
+ }
1335
+ if (document.querySelector(`link[${MARKER}]`)) return;
1336
+ const link = document.createElement("link");
1337
+ link.rel = "stylesheet";
1338
+ link.href = FONT_HREF;
1339
+ link.setAttribute(MARKER, "");
1340
+ document.head.appendChild(link);
1341
+ }
1342
+
1317
1343
  // src/doodle.js
1318
1344
  var instances = /* @__PURE__ */ new WeakMap();
1319
1345
  function localViewport(overlayRect, inset = 8) {
@@ -1365,7 +1391,12 @@ var DoodleOverlay = class {
1365
1391
  }
1366
1392
  window.addEventListener("resize", this.onResize, { passive: true });
1367
1393
  this.scrollTargets.push({ target: window, type: "resize" });
1368
- document.fonts?.ready?.then(() => this.scheduleUpdate()).catch(() => {
1394
+ if (this.options.note && this.options.autoLoadFont && !this.options.fontFamily) {
1395
+ ensureHandwrittenFont();
1396
+ }
1397
+ this.onFontsLoaded = () => this.scheduleUpdate();
1398
+ document.fonts?.addEventListener?.("loadingdone", this.onFontsLoaded);
1399
+ document.fonts?.ready?.then(this.onFontsLoaded).catch(() => {
1369
1400
  });
1370
1401
  this.update();
1371
1402
  }
@@ -1408,7 +1439,13 @@ var DoodleOverlay = class {
1408
1439
  this.svg.style.width = `${overlayRect.width}px`;
1409
1440
  this.svg.style.height = `${overlayRect.height}px`;
1410
1441
  const strokeStyle = { color, strokeWidth, roughness, opacity };
1411
- const noteStyle = { color, opacity, strokeWidth, roughness };
1442
+ const noteStyle = {
1443
+ color,
1444
+ opacity,
1445
+ strokeWidth,
1446
+ roughness,
1447
+ fontFamily: this.options.fontFamily
1448
+ };
1412
1449
  const noteGroups = [];
1413
1450
  const noteLayout = {
1414
1451
  viewport: localViewport(overlayRect),
@@ -1483,6 +1520,10 @@ var DoodleOverlay = class {
1483
1520
  this.mutationObserver.disconnect();
1484
1521
  this.mutationObserver = null;
1485
1522
  }
1523
+ if (this.onFontsLoaded) {
1524
+ document.fonts?.removeEventListener?.("loadingdone", this.onFontsLoaded);
1525
+ this.onFontsLoaded = null;
1526
+ }
1486
1527
  for (const { target, type } of this.scrollTargets) {
1487
1528
  target.removeEventListener(type, type === "scroll" ? this.onScroll : this.onResize);
1488
1529
  }
@@ -4,14 +4,19 @@ var DEFAULT_OPTIONS = {
4
4
  color: "#ffffff",
5
5
  strokeWidth: 1.5,
6
6
  roughness: 1.5,
7
- padding: 8,
7
+ // The frame traces the element's own edge. Raise this to stand it off.
8
+ padding: 0,
8
9
  radius: null,
9
10
  opacity: 0.9,
10
11
  children: null,
11
12
  note: null,
12
13
  arrow: false,
13
14
  decorations: false,
14
- addBreaks: false
15
+ addBreaks: false,
16
+ /** Override the handwriting stack, e.g. a next/font CSS variable. */
17
+ fontFamily: null,
18
+ /** Fetch Caveat when the page has not provided it. */
19
+ autoLoadFont: true
15
20
  };
16
21
  function resolveElement(target) {
17
22
  if (!target) return null;
@@ -237,7 +242,7 @@ function annotationAnchor(position, rect, offset = 12) {
237
242
 
238
243
  // src/renderer.js
239
244
  var SVG_NS = "http://www.w3.org/2000/svg";
240
- var HANDWRITTEN_FONT = '"Caveat", "Kalam", "Patrick Hand", cursive';
245
+ var HANDWRITTEN_FONT = '"Caveat", "Kalam", "Patrick Hand", "Bradley Hand", "Segoe Script", "Comic Sans MS", cursive';
241
246
  var NOTE_FONT_SIZE = 19;
242
247
  function clearSvg(svg) {
243
248
  while (svg.firstChild) {
@@ -716,7 +721,7 @@ function createNoteText(svg, text, style) {
716
721
  textEl.setAttribute("fill", style.color);
717
722
  textEl.setAttribute("opacity", String(style.opacity));
718
723
  textEl.setAttribute("font-size", String(fontSize));
719
- textEl.setAttribute("font-family", HANDWRITTEN_FONT);
724
+ textEl.setAttribute("font-family", style.fontFamily || HANDWRITTEN_FONT);
720
725
  textEl.setAttribute("font-weight", "600");
721
726
  textEl.setAttribute("letter-spacing", "0.4");
722
727
  textEl.setAttribute("text-anchor", "start");
@@ -1289,6 +1294,27 @@ function renderDecorations(svg, rect, decorationOptions, style, seed, context =
1289
1294
  }
1290
1295
  }
1291
1296
 
1297
+ // src/fonts.js
1298
+ var FONT_NAME = "Caveat";
1299
+ var FONT_HREF = "https://fonts.googleapis.com/css2?family=Caveat:wght@400;600&display=swap";
1300
+ var MARKER = "data-freehand-font";
1301
+ var requested = false;
1302
+ function ensureHandwrittenFont() {
1303
+ if (requested) return;
1304
+ if (typeof document === "undefined" || !document.head) return;
1305
+ requested = true;
1306
+ try {
1307
+ if (document.fonts?.check?.(`600 19px "${FONT_NAME}"`)) return;
1308
+ } catch {
1309
+ }
1310
+ if (document.querySelector(`link[${MARKER}]`)) return;
1311
+ const link = document.createElement("link");
1312
+ link.rel = "stylesheet";
1313
+ link.href = FONT_HREF;
1314
+ link.setAttribute(MARKER, "");
1315
+ document.head.appendChild(link);
1316
+ }
1317
+
1292
1318
  // src/doodle.js
1293
1319
  var instances = /* @__PURE__ */ new WeakMap();
1294
1320
  function localViewport(overlayRect, inset = 8) {
@@ -1340,7 +1366,12 @@ var DoodleOverlay = class {
1340
1366
  }
1341
1367
  window.addEventListener("resize", this.onResize, { passive: true });
1342
1368
  this.scrollTargets.push({ target: window, type: "resize" });
1343
- document.fonts?.ready?.then(() => this.scheduleUpdate()).catch(() => {
1369
+ if (this.options.note && this.options.autoLoadFont && !this.options.fontFamily) {
1370
+ ensureHandwrittenFont();
1371
+ }
1372
+ this.onFontsLoaded = () => this.scheduleUpdate();
1373
+ document.fonts?.addEventListener?.("loadingdone", this.onFontsLoaded);
1374
+ document.fonts?.ready?.then(this.onFontsLoaded).catch(() => {
1344
1375
  });
1345
1376
  this.update();
1346
1377
  }
@@ -1383,7 +1414,13 @@ var DoodleOverlay = class {
1383
1414
  this.svg.style.width = `${overlayRect.width}px`;
1384
1415
  this.svg.style.height = `${overlayRect.height}px`;
1385
1416
  const strokeStyle = { color, strokeWidth, roughness, opacity };
1386
- const noteStyle = { color, opacity, strokeWidth, roughness };
1417
+ const noteStyle = {
1418
+ color,
1419
+ opacity,
1420
+ strokeWidth,
1421
+ roughness,
1422
+ fontFamily: this.options.fontFamily
1423
+ };
1387
1424
  const noteGroups = [];
1388
1425
  const noteLayout = {
1389
1426
  viewport: localViewport(overlayRect),
@@ -1458,6 +1495,10 @@ var DoodleOverlay = class {
1458
1495
  this.mutationObserver.disconnect();
1459
1496
  this.mutationObserver = null;
1460
1497
  }
1498
+ if (this.onFontsLoaded) {
1499
+ document.fonts?.removeEventListener?.("loadingdone", this.onFontsLoaded);
1500
+ this.onFontsLoaded = null;
1501
+ }
1461
1502
  for (const { target, type } of this.scrollTargets) {
1462
1503
  target.removeEventListener(type, type === "scroll" ? this.onScroll : this.onResize);
1463
1504
  }
package/dist/index.d.ts CHANGED
@@ -59,6 +59,7 @@ export interface DoodleOptions {
59
59
  color?: string;
60
60
  strokeWidth?: number;
61
61
  roughness?: number;
62
+ /** Gap between the element's edge and the frame. Default `0`. */
62
63
  padding?: number;
63
64
  /** Auto-detected from CSS when omitted. */
64
65
  radius?: number | null;
@@ -70,6 +71,18 @@ export interface DoodleOptions {
70
71
  decorations?: boolean | DecorationOptions;
71
72
  /** Lift the pen at random points around the outline. */
72
73
  addBreaks?: boolean | number | BreakOptions;
74
+ /**
75
+ * Font stack for handwritten notes. Point this at your own family — a
76
+ * `next/font` CSS variable, for instance — to take over from the built-in one.
77
+ */
78
+ fontFamily?: string | null;
79
+ /**
80
+ * Fetch the Caveat webfont when the page has not already provided it.
81
+ * Default `true`. Set `false` if a strict CSP blocks Google Fonts, or if you
82
+ * self-host the family; the notes then use `fontFamily` or the system
83
+ * handwriting fallbacks.
84
+ */
85
+ autoLoadFont?: boolean;
73
86
  }
74
87
 
75
88
  export interface DoodleInstance {
package/dist/react.cjs CHANGED
@@ -34,14 +34,19 @@ var DEFAULT_OPTIONS = {
34
34
  color: "#ffffff",
35
35
  strokeWidth: 1.5,
36
36
  roughness: 1.5,
37
- padding: 8,
37
+ // The frame traces the element's own edge. Raise this to stand it off.
38
+ padding: 0,
38
39
  radius: null,
39
40
  opacity: 0.9,
40
41
  children: null,
41
42
  note: null,
42
43
  arrow: false,
43
44
  decorations: false,
44
- addBreaks: false
45
+ addBreaks: false,
46
+ /** Override the handwriting stack, e.g. a next/font CSS variable. */
47
+ fontFamily: null,
48
+ /** Fetch Caveat when the page has not provided it. */
49
+ autoLoadFont: true
45
50
  };
46
51
  function resolveElement(target) {
47
52
  if (!target) return null;
@@ -267,7 +272,7 @@ function annotationAnchor(position, rect, offset = 12) {
267
272
 
268
273
  // src/renderer.js
269
274
  var SVG_NS = "http://www.w3.org/2000/svg";
270
- var HANDWRITTEN_FONT = '"Caveat", "Kalam", "Patrick Hand", cursive';
275
+ var HANDWRITTEN_FONT = '"Caveat", "Kalam", "Patrick Hand", "Bradley Hand", "Segoe Script", "Comic Sans MS", cursive';
271
276
  var NOTE_FONT_SIZE = 19;
272
277
  function clearSvg(svg) {
273
278
  while (svg.firstChild) {
@@ -746,7 +751,7 @@ function createNoteText(svg, text, style) {
746
751
  textEl.setAttribute("fill", style.color);
747
752
  textEl.setAttribute("opacity", String(style.opacity));
748
753
  textEl.setAttribute("font-size", String(fontSize));
749
- textEl.setAttribute("font-family", HANDWRITTEN_FONT);
754
+ textEl.setAttribute("font-family", style.fontFamily || HANDWRITTEN_FONT);
750
755
  textEl.setAttribute("font-weight", "600");
751
756
  textEl.setAttribute("letter-spacing", "0.4");
752
757
  textEl.setAttribute("text-anchor", "start");
@@ -1319,6 +1324,27 @@ function renderDecorations(svg, rect, decorationOptions, style, seed, context =
1319
1324
  }
1320
1325
  }
1321
1326
 
1327
+ // src/fonts.js
1328
+ var FONT_NAME = "Caveat";
1329
+ var FONT_HREF = "https://fonts.googleapis.com/css2?family=Caveat:wght@400;600&display=swap";
1330
+ var MARKER = "data-freehand-font";
1331
+ var requested = false;
1332
+ function ensureHandwrittenFont() {
1333
+ if (requested) return;
1334
+ if (typeof document === "undefined" || !document.head) return;
1335
+ requested = true;
1336
+ try {
1337
+ if (document.fonts?.check?.(`600 19px "${FONT_NAME}"`)) return;
1338
+ } catch {
1339
+ }
1340
+ if (document.querySelector(`link[${MARKER}]`)) return;
1341
+ const link = document.createElement("link");
1342
+ link.rel = "stylesheet";
1343
+ link.href = FONT_HREF;
1344
+ link.setAttribute(MARKER, "");
1345
+ document.head.appendChild(link);
1346
+ }
1347
+
1322
1348
  // src/doodle.js
1323
1349
  var instances = /* @__PURE__ */ new WeakMap();
1324
1350
  function localViewport(overlayRect, inset = 8) {
@@ -1370,7 +1396,12 @@ var DoodleOverlay = class {
1370
1396
  }
1371
1397
  window.addEventListener("resize", this.onResize, { passive: true });
1372
1398
  this.scrollTargets.push({ target: window, type: "resize" });
1373
- document.fonts?.ready?.then(() => this.scheduleUpdate()).catch(() => {
1399
+ if (this.options.note && this.options.autoLoadFont && !this.options.fontFamily) {
1400
+ ensureHandwrittenFont();
1401
+ }
1402
+ this.onFontsLoaded = () => this.scheduleUpdate();
1403
+ document.fonts?.addEventListener?.("loadingdone", this.onFontsLoaded);
1404
+ document.fonts?.ready?.then(this.onFontsLoaded).catch(() => {
1374
1405
  });
1375
1406
  this.update();
1376
1407
  }
@@ -1413,7 +1444,13 @@ var DoodleOverlay = class {
1413
1444
  this.svg.style.width = `${overlayRect.width}px`;
1414
1445
  this.svg.style.height = `${overlayRect.height}px`;
1415
1446
  const strokeStyle = { color, strokeWidth, roughness, opacity };
1416
- const noteStyle = { color, opacity, strokeWidth, roughness };
1447
+ const noteStyle = {
1448
+ color,
1449
+ opacity,
1450
+ strokeWidth,
1451
+ roughness,
1452
+ fontFamily: this.options.fontFamily
1453
+ };
1417
1454
  const noteGroups = [];
1418
1455
  const noteLayout = {
1419
1456
  viewport: localViewport(overlayRect),
@@ -1488,6 +1525,10 @@ var DoodleOverlay = class {
1488
1525
  this.mutationObserver.disconnect();
1489
1526
  this.mutationObserver = null;
1490
1527
  }
1528
+ if (this.onFontsLoaded) {
1529
+ document.fonts?.removeEventListener?.("loadingdone", this.onFontsLoaded);
1530
+ this.onFontsLoaded = null;
1531
+ }
1491
1532
  for (const { target, type } of this.scrollTargets) {
1492
1533
  target.removeEventListener(type, type === "scroll" ? this.onScroll : this.onResize);
1493
1534
  }
@@ -1530,7 +1571,9 @@ var OPTION_KEYS = [
1530
1571
  "note",
1531
1572
  "arrow",
1532
1573
  "decorations",
1533
- "addBreaks"
1574
+ "addBreaks",
1575
+ "fontFamily",
1576
+ "autoLoadFont"
1534
1577
  ];
1535
1578
  function collectOptions(props) {
1536
1579
  const options = {};
@@ -1580,6 +1623,8 @@ var Doodle = (0, import_react.forwardRef)(function Doodle2(props, forwardedRef)
1580
1623
  arrow,
1581
1624
  decorations,
1582
1625
  addBreaks,
1626
+ fontFamily,
1627
+ autoLoadFont,
1583
1628
  childSelector,
1584
1629
  ...rest
1585
1630
  } = props;
@@ -1597,6 +1642,8 @@ var Doodle = (0, import_react.forwardRef)(function Doodle2(props, forwardedRef)
1597
1642
  arrow,
1598
1643
  decorations,
1599
1644
  addBreaks,
1645
+ fontFamily,
1646
+ autoLoadFont,
1600
1647
  childSelector
1601
1648
  }),
1602
1649
  [
@@ -1612,6 +1659,8 @@ var Doodle = (0, import_react.forwardRef)(function Doodle2(props, forwardedRef)
1612
1659
  JSON.stringify(arrow ?? null),
1613
1660
  JSON.stringify(decorations ?? null),
1614
1661
  JSON.stringify(addBreaks ?? null),
1662
+ fontFamily,
1663
+ autoLoadFont,
1615
1664
  childSelector
1616
1665
  ]
1617
1666
  );
package/dist/react.js CHANGED
@@ -17,14 +17,19 @@ var DEFAULT_OPTIONS = {
17
17
  color: "#ffffff",
18
18
  strokeWidth: 1.5,
19
19
  roughness: 1.5,
20
- padding: 8,
20
+ // The frame traces the element's own edge. Raise this to stand it off.
21
+ padding: 0,
21
22
  radius: null,
22
23
  opacity: 0.9,
23
24
  children: null,
24
25
  note: null,
25
26
  arrow: false,
26
27
  decorations: false,
27
- addBreaks: false
28
+ addBreaks: false,
29
+ /** Override the handwriting stack, e.g. a next/font CSS variable. */
30
+ fontFamily: null,
31
+ /** Fetch Caveat when the page has not provided it. */
32
+ autoLoadFont: true
28
33
  };
29
34
  function resolveElement(target) {
30
35
  if (!target) return null;
@@ -250,7 +255,7 @@ function annotationAnchor(position, rect, offset = 12) {
250
255
 
251
256
  // src/renderer.js
252
257
  var SVG_NS = "http://www.w3.org/2000/svg";
253
- var HANDWRITTEN_FONT = '"Caveat", "Kalam", "Patrick Hand", cursive';
258
+ var HANDWRITTEN_FONT = '"Caveat", "Kalam", "Patrick Hand", "Bradley Hand", "Segoe Script", "Comic Sans MS", cursive';
254
259
  var NOTE_FONT_SIZE = 19;
255
260
  function clearSvg(svg) {
256
261
  while (svg.firstChild) {
@@ -729,7 +734,7 @@ function createNoteText(svg, text, style) {
729
734
  textEl.setAttribute("fill", style.color);
730
735
  textEl.setAttribute("opacity", String(style.opacity));
731
736
  textEl.setAttribute("font-size", String(fontSize));
732
- textEl.setAttribute("font-family", HANDWRITTEN_FONT);
737
+ textEl.setAttribute("font-family", style.fontFamily || HANDWRITTEN_FONT);
733
738
  textEl.setAttribute("font-weight", "600");
734
739
  textEl.setAttribute("letter-spacing", "0.4");
735
740
  textEl.setAttribute("text-anchor", "start");
@@ -1302,6 +1307,27 @@ function renderDecorations(svg, rect, decorationOptions, style, seed, context =
1302
1307
  }
1303
1308
  }
1304
1309
 
1310
+ // src/fonts.js
1311
+ var FONT_NAME = "Caveat";
1312
+ var FONT_HREF = "https://fonts.googleapis.com/css2?family=Caveat:wght@400;600&display=swap";
1313
+ var MARKER = "data-freehand-font";
1314
+ var requested = false;
1315
+ function ensureHandwrittenFont() {
1316
+ if (requested) return;
1317
+ if (typeof document === "undefined" || !document.head) return;
1318
+ requested = true;
1319
+ try {
1320
+ if (document.fonts?.check?.(`600 19px "${FONT_NAME}"`)) return;
1321
+ } catch {
1322
+ }
1323
+ if (document.querySelector(`link[${MARKER}]`)) return;
1324
+ const link = document.createElement("link");
1325
+ link.rel = "stylesheet";
1326
+ link.href = FONT_HREF;
1327
+ link.setAttribute(MARKER, "");
1328
+ document.head.appendChild(link);
1329
+ }
1330
+
1305
1331
  // src/doodle.js
1306
1332
  var instances = /* @__PURE__ */ new WeakMap();
1307
1333
  function localViewport(overlayRect, inset = 8) {
@@ -1353,7 +1379,12 @@ var DoodleOverlay = class {
1353
1379
  }
1354
1380
  window.addEventListener("resize", this.onResize, { passive: true });
1355
1381
  this.scrollTargets.push({ target: window, type: "resize" });
1356
- document.fonts?.ready?.then(() => this.scheduleUpdate()).catch(() => {
1382
+ if (this.options.note && this.options.autoLoadFont && !this.options.fontFamily) {
1383
+ ensureHandwrittenFont();
1384
+ }
1385
+ this.onFontsLoaded = () => this.scheduleUpdate();
1386
+ document.fonts?.addEventListener?.("loadingdone", this.onFontsLoaded);
1387
+ document.fonts?.ready?.then(this.onFontsLoaded).catch(() => {
1357
1388
  });
1358
1389
  this.update();
1359
1390
  }
@@ -1396,7 +1427,13 @@ var DoodleOverlay = class {
1396
1427
  this.svg.style.width = `${overlayRect.width}px`;
1397
1428
  this.svg.style.height = `${overlayRect.height}px`;
1398
1429
  const strokeStyle = { color, strokeWidth, roughness, opacity };
1399
- const noteStyle = { color, opacity, strokeWidth, roughness };
1430
+ const noteStyle = {
1431
+ color,
1432
+ opacity,
1433
+ strokeWidth,
1434
+ roughness,
1435
+ fontFamily: this.options.fontFamily
1436
+ };
1400
1437
  const noteGroups = [];
1401
1438
  const noteLayout = {
1402
1439
  viewport: localViewport(overlayRect),
@@ -1471,6 +1508,10 @@ var DoodleOverlay = class {
1471
1508
  this.mutationObserver.disconnect();
1472
1509
  this.mutationObserver = null;
1473
1510
  }
1511
+ if (this.onFontsLoaded) {
1512
+ document.fonts?.removeEventListener?.("loadingdone", this.onFontsLoaded);
1513
+ this.onFontsLoaded = null;
1514
+ }
1474
1515
  for (const { target, type } of this.scrollTargets) {
1475
1516
  target.removeEventListener(type, type === "scroll" ? this.onScroll : this.onResize);
1476
1517
  }
@@ -1513,7 +1554,9 @@ var OPTION_KEYS = [
1513
1554
  "note",
1514
1555
  "arrow",
1515
1556
  "decorations",
1516
- "addBreaks"
1557
+ "addBreaks",
1558
+ "fontFamily",
1559
+ "autoLoadFont"
1517
1560
  ];
1518
1561
  function collectOptions(props) {
1519
1562
  const options = {};
@@ -1563,6 +1606,8 @@ var Doodle = forwardRef(function Doodle2(props, forwardedRef) {
1563
1606
  arrow,
1564
1607
  decorations,
1565
1608
  addBreaks,
1609
+ fontFamily,
1610
+ autoLoadFont,
1566
1611
  childSelector,
1567
1612
  ...rest
1568
1613
  } = props;
@@ -1580,6 +1625,8 @@ var Doodle = forwardRef(function Doodle2(props, forwardedRef) {
1580
1625
  arrow,
1581
1626
  decorations,
1582
1627
  addBreaks,
1628
+ fontFamily,
1629
+ autoLoadFont,
1583
1630
  childSelector
1584
1631
  }),
1585
1632
  [
@@ -1595,6 +1642,8 @@ var Doodle = forwardRef(function Doodle2(props, forwardedRef) {
1595
1642
  JSON.stringify(arrow ?? null),
1596
1643
  JSON.stringify(decorations ?? null),
1597
1644
  JSON.stringify(addBreaks ?? null),
1645
+ fontFamily,
1646
+ autoLoadFont,
1598
1647
  childSelector
1599
1648
  ]
1600
1649
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aznabee/freehand-ui",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
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",