@aznabee/freehand-ui 0.1.8 → 0.2.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 CHANGED
@@ -424,7 +424,41 @@ Server-rendered markup is unaffected: the overlay is drawn in an effect, so the
424
424
 
425
425
  - 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.
426
426
  - `disabled` skips drawing entirely - useful behind a reduced-motion check or a feature flag.
427
- - TypeScript definitions ship with the package for both entry points.
427
+ - TypeScript definitions ship with the package for every entry point.
428
+
429
+ ---
430
+
431
+ ## React Native
432
+
433
+ The same doodles, drawn with [react-native-svg](https://github.com/software-mansion/react-native-svg). Import from the `native` entry point - it needs `react-native-svg` installed in the app.
434
+
435
+ ```bash
436
+ npm install @aznabee/freehand-ui react-native-svg
437
+ ```
438
+
439
+ ```tsx
440
+ import Doodle from "@aznabee/freehand-ui/native";
441
+
442
+ <Doodle
443
+ note={{ text: "start chat", position: "bottom", underline: false }}
444
+ arrow={{ from: "left", to: "edge", style: "curved" }}
445
+ decorations={{ types: ["heart"] }}
446
+ radius={16}
447
+ >
448
+ <TouchableOpacity onPress={start}>...</TouchableOpacity>
449
+ </Doodle>
450
+ ```
451
+
452
+ `<Doodle>` is a plain `View` - every `View` prop passes through - and takes the same options as the web component. The doodle is drawn in an absolutely positioned layer that ignores touches and never affects layout, so, as on the web, an ancestor with `overflow: "hidden"` clips it.
453
+
454
+ Differences from the web:
455
+
456
+ - **Corner radius** is not detected from the children. Pass `radius`, or it is read from the wrapper's own `borderRadius` style.
457
+ - **Handwriting** uses the platform's built-in face - Noteworthy on iOS, `casual` on Android - because nothing is fetched at runtime. Link Caveat into the app and pass `fontFamily="Caveat"` to match the web exactly.
458
+ - **Note width** is estimated from the glyphs rather than measured, which is close enough to place the note and its underline.
459
+ - **Arrows** draw on and lean as they do on the web (the lean runs on the native driver). A dotted shaft stays still instead of crawling. Both are skipped when the OS asks for reduced motion.
460
+ - **Language**: React Native has no `navigator.languages`, so pass `locale` (your app's i18n language) to choose from a multilingual note.
461
+ - `childSelector` has no equivalent.
428
462
 
429
463
  ---
430
464
 
@@ -577,8 +577,13 @@ function annotationAnchor(position, rect, offset = 12) {
577
577
  return point;
578
578
  }
579
579
 
580
- // src/renderer.js
580
+ // src/svg-dom.js
581
581
  var SVG_NS = "http://www.w3.org/2000/svg";
582
+ function createSvgElement(tag) {
583
+ return document.createElementNS(SVG_NS, tag);
584
+ }
585
+
586
+ // src/renderer.js
582
587
  var XML_NS = "http://www.w3.org/XML/1998/namespace";
583
588
  var HANDWRITTEN_FONT = '"Caveat", "Kalam", "Patrick Hand", "Bradley Hand", "Segoe Script", "Comic Sans MS", cursive';
584
589
  var NOTE_FONT_SIZE = 19;
@@ -802,7 +807,7 @@ function createBorderBreaks(perimeter, config, random) {
802
807
  return breaks;
803
808
  }
804
809
  function createGroup(parent, x, y, rotation = 0) {
805
- const group = document.createElementNS(SVG_NS, "g");
810
+ const group = createSvgElement("g");
806
811
  group.setAttribute(
807
812
  "transform",
808
813
  `translate(${fmt(x)} ${fmt(y)}) rotate(${rotation.toFixed(1)})`
@@ -811,7 +816,7 @@ function createGroup(parent, x, y, rotation = 0) {
811
816
  return group;
812
817
  }
813
818
  function appendPath(svg, d, style) {
814
- const path = document.createElementNS(SVG_NS, "path");
819
+ const path = createSvgElement("path");
815
820
  path.setAttribute("d", d);
816
821
  path.setAttribute("fill", style.fill ?? "none");
817
822
  path.setAttribute("stroke", style.color);
@@ -826,7 +831,7 @@ function appendPath(svg, d, style) {
826
831
  return path;
827
832
  }
828
833
  function appendGlow(svg, radius, style) {
829
- const circle = document.createElementNS(SVG_NS, "circle");
834
+ const circle = createSvgElement("circle");
830
835
  circle.setAttribute("r", fmt(radius));
831
836
  circle.setAttribute("fill", style.color);
832
837
  circle.setAttribute("opacity", String(style.opacity));
@@ -1130,7 +1135,7 @@ function drawArrow(svg, rect, arrowOptions, style, seed, origin = null, context
1130
1135
  tipAngle = Math.atan2(end.y - c2y, end.x - c2x);
1131
1136
  }
1132
1137
  }
1133
- const group = document.createElementNS(SVG_NS, "g");
1138
+ const group = createSvgElement("g");
1134
1139
  svg.appendChild(group);
1135
1140
  const shaftPaths = [];
1136
1141
  if (ghost) {
@@ -1220,8 +1225,8 @@ function createNoteText(svg, text, style, options = {}) {
1220
1225
  const lines = wrapWords(text, options.wordsPerLine ?? DEFAULT_WORDS_PER_LINE);
1221
1226
  const lineHeight = fontSize * 1.05;
1222
1227
  const rtl = options.rtl ?? isRtlText(text);
1223
- const group = document.createElementNS(SVG_NS, "g");
1224
- const textEl = document.createElementNS(SVG_NS, "text");
1228
+ const group = createSvgElement("g");
1229
+ const textEl = createSvgElement("text");
1225
1230
  textEl.setAttribute("x", "0");
1226
1231
  textEl.setAttribute("y", "0");
1227
1232
  textEl.setAttribute("fill", style.color);
@@ -1240,7 +1245,7 @@ function createNoteText(svg, text, style, options = {}) {
1240
1245
  textEl.setAttributeNS(XML_NS, "xml:lang", options.locale);
1241
1246
  }
1242
1247
  const tspans = lines.map((line, index) => {
1243
- const tspan = document.createElementNS(SVG_NS, "tspan");
1248
+ const tspan = createSvgElement("tspan");
1244
1249
  tspan.textContent = line;
1245
1250
  tspan.setAttribute("x", "0");
1246
1251
  tspan.setAttribute("dy", index === 0 ? "0" : String(lineHeight));
@@ -1304,7 +1309,7 @@ function placeNoteText(note, box, style, seed, options = {}) {
1304
1309
  return note.group;
1305
1310
  }
1306
1311
  function createOverlaySvg(left, top, width, height) {
1307
- const svg = document.createElementNS(SVG_NS, "svg");
1312
+ const svg = createSvgElement("svg");
1308
1313
  svg.setAttribute("class", OVERLAY_CLASS);
1309
1314
  svg.setAttribute("xmlns", SVG_NS);
1310
1315
  svg.setAttribute("width", String(width));
@@ -552,8 +552,13 @@ function annotationAnchor(position, rect, offset = 12) {
552
552
  return point;
553
553
  }
554
554
 
555
- // src/renderer.js
555
+ // src/svg-dom.js
556
556
  var SVG_NS = "http://www.w3.org/2000/svg";
557
+ function createSvgElement(tag) {
558
+ return document.createElementNS(SVG_NS, tag);
559
+ }
560
+
561
+ // src/renderer.js
557
562
  var XML_NS = "http://www.w3.org/XML/1998/namespace";
558
563
  var HANDWRITTEN_FONT = '"Caveat", "Kalam", "Patrick Hand", "Bradley Hand", "Segoe Script", "Comic Sans MS", cursive';
559
564
  var NOTE_FONT_SIZE = 19;
@@ -777,7 +782,7 @@ function createBorderBreaks(perimeter, config, random) {
777
782
  return breaks;
778
783
  }
779
784
  function createGroup(parent, x, y, rotation = 0) {
780
- const group = document.createElementNS(SVG_NS, "g");
785
+ const group = createSvgElement("g");
781
786
  group.setAttribute(
782
787
  "transform",
783
788
  `translate(${fmt(x)} ${fmt(y)}) rotate(${rotation.toFixed(1)})`
@@ -786,7 +791,7 @@ function createGroup(parent, x, y, rotation = 0) {
786
791
  return group;
787
792
  }
788
793
  function appendPath(svg, d, style) {
789
- const path = document.createElementNS(SVG_NS, "path");
794
+ const path = createSvgElement("path");
790
795
  path.setAttribute("d", d);
791
796
  path.setAttribute("fill", style.fill ?? "none");
792
797
  path.setAttribute("stroke", style.color);
@@ -801,7 +806,7 @@ function appendPath(svg, d, style) {
801
806
  return path;
802
807
  }
803
808
  function appendGlow(svg, radius, style) {
804
- const circle = document.createElementNS(SVG_NS, "circle");
809
+ const circle = createSvgElement("circle");
805
810
  circle.setAttribute("r", fmt(radius));
806
811
  circle.setAttribute("fill", style.color);
807
812
  circle.setAttribute("opacity", String(style.opacity));
@@ -1105,7 +1110,7 @@ function drawArrow(svg, rect, arrowOptions, style, seed, origin = null, context
1105
1110
  tipAngle = Math.atan2(end.y - c2y, end.x - c2x);
1106
1111
  }
1107
1112
  }
1108
- const group = document.createElementNS(SVG_NS, "g");
1113
+ const group = createSvgElement("g");
1109
1114
  svg.appendChild(group);
1110
1115
  const shaftPaths = [];
1111
1116
  if (ghost) {
@@ -1195,8 +1200,8 @@ function createNoteText(svg, text, style, options = {}) {
1195
1200
  const lines = wrapWords(text, options.wordsPerLine ?? DEFAULT_WORDS_PER_LINE);
1196
1201
  const lineHeight = fontSize * 1.05;
1197
1202
  const rtl = options.rtl ?? isRtlText(text);
1198
- const group = document.createElementNS(SVG_NS, "g");
1199
- const textEl = document.createElementNS(SVG_NS, "text");
1203
+ const group = createSvgElement("g");
1204
+ const textEl = createSvgElement("text");
1200
1205
  textEl.setAttribute("x", "0");
1201
1206
  textEl.setAttribute("y", "0");
1202
1207
  textEl.setAttribute("fill", style.color);
@@ -1215,7 +1220,7 @@ function createNoteText(svg, text, style, options = {}) {
1215
1220
  textEl.setAttributeNS(XML_NS, "xml:lang", options.locale);
1216
1221
  }
1217
1222
  const tspans = lines.map((line, index) => {
1218
- const tspan = document.createElementNS(SVG_NS, "tspan");
1223
+ const tspan = createSvgElement("tspan");
1219
1224
  tspan.textContent = line;
1220
1225
  tspan.setAttribute("x", "0");
1221
1226
  tspan.setAttribute("dy", index === 0 ? "0" : String(lineHeight));
@@ -1279,7 +1284,7 @@ function placeNoteText(note, box, style, seed, options = {}) {
1279
1284
  return note.group;
1280
1285
  }
1281
1286
  function createOverlaySvg(left, top, width, height) {
1282
- const svg = document.createElementNS(SVG_NS, "svg");
1287
+ const svg = createSvgElement("svg");
1283
1288
  svg.setAttribute("class", OVERLAY_CLASS);
1284
1289
  svg.setAttribute("xmlns", SVG_NS);
1285
1290
  svg.setAttribute("width", String(width));