@aznabee/freehand-ui 0.1.8 → 0.2.1
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 +35 -1
- package/dist/freehand-ui.cjs +14 -9
- package/dist/freehand-ui.js +14 -9
- package/dist/native.cjs +2422 -0
- package/dist/native.d.ts +51 -0
- package/dist/native.js +2428 -0
- package/dist/react.cjs +14 -9
- package/dist/react.js +14 -9
- package/native/package.json +7 -0
- package/package.json +18 -2
- package/react/package.json +7 -0
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
|
|
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
|
|
package/dist/freehand-ui.cjs
CHANGED
|
@@ -577,8 +577,13 @@ function annotationAnchor(position, rect, offset = 12) {
|
|
|
577
577
|
return point;
|
|
578
578
|
}
|
|
579
579
|
|
|
580
|
-
// src/
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1224
|
-
const textEl =
|
|
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 =
|
|
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 =
|
|
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));
|
package/dist/freehand-ui.js
CHANGED
|
@@ -552,8 +552,13 @@ function annotationAnchor(position, rect, offset = 12) {
|
|
|
552
552
|
return point;
|
|
553
553
|
}
|
|
554
554
|
|
|
555
|
-
// src/
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1199
|
-
const textEl =
|
|
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 =
|
|
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 =
|
|
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));
|