@inkra/react 0.0.0-stage → 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.
Files changed (267) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +316 -2
  3. package/THIRD_PARTY_NOTICES.md +39 -0
  4. package/Yozai-OFL.txt +94 -0
  5. package/dist/Button.d.ts +36 -0
  6. package/dist/Button.js +89 -0
  7. package/dist/PencilIcon.d.ts +22 -0
  8. package/dist/PencilIcon.js +210 -0
  9. package/dist/PencilLine.d.ts +10 -0
  10. package/dist/PencilLine.js +91 -0
  11. package/dist/PencilSurface.d.ts +19 -0
  12. package/dist/PencilSurface.js +19 -0
  13. package/dist/PencilText.d.ts +16 -0
  14. package/dist/PencilText.js +338 -0
  15. package/dist/accordion/Accordion.d.ts +27 -0
  16. package/dist/accordion/Accordion.js +40 -0
  17. package/dist/accordion/index.d.ts +2 -0
  18. package/dist/accordion/index.js +1 -0
  19. package/dist/alert-dialog/index.d.ts +64 -0
  20. package/dist/alert-dialog/index.js +109 -0
  21. package/dist/assets/ui-hand-yozai.woff2 +0 -0
  22. package/dist/audio-player/index.d.ts +60 -0
  23. package/dist/audio-player/index.js +101 -0
  24. package/dist/breadcrumb/index.d.ts +16 -0
  25. package/dist/breadcrumb/index.js +25 -0
  26. package/dist/calendar/index.d.ts +21 -0
  27. package/dist/calendar/index.js +41 -0
  28. package/dist/carousel/index.d.ts +51 -0
  29. package/dist/carousel/index.js +290 -0
  30. package/dist/choice-grid/index.d.ts +38 -0
  31. package/dist/choice-grid/index.js +85 -0
  32. package/dist/color-picker/color.d.ts +15 -0
  33. package/dist/color-picker/color.js +55 -0
  34. package/dist/color-picker/index.d.ts +31 -0
  35. package/dist/color-picker/index.js +69 -0
  36. package/dist/command/index.d.ts +23 -0
  37. package/dist/command/index.js +39 -0
  38. package/dist/content/index.d.ts +106 -0
  39. package/dist/content/index.js +80 -0
  40. package/dist/context-menu/index.d.ts +76 -0
  41. package/dist/context-menu/index.js +240 -0
  42. package/dist/data-table/index.d.ts +47 -0
  43. package/dist/data-table/index.js +91 -0
  44. package/dist/date-picker/index.d.ts +50 -0
  45. package/dist/date-picker/index.js +180 -0
  46. package/dist/day-agenda/index.d.ts +43 -0
  47. package/dist/day-agenda/index.js +47 -0
  48. package/dist/day-agenda/layout.d.ts +20 -0
  49. package/dist/day-agenda/layout.js +60 -0
  50. package/dist/display/ProgressRing.d.ts +6 -0
  51. package/dist/display/ProgressRing.js +146 -0
  52. package/dist/display/index.d.ts +59 -0
  53. package/dist/display/index.js +75 -0
  54. package/dist/dockable-panel/index.d.ts +41 -0
  55. package/dist/dockable-panel/index.js +253 -0
  56. package/dist/dockable-panel/model.d.ts +21 -0
  57. package/dist/dockable-panel/model.js +61 -0
  58. package/dist/drawer/index.d.ts +63 -0
  59. package/dist/drawer/index.js +86 -0
  60. package/dist/dropdown-menu/index.d.ts +34 -0
  61. package/dist/dropdown-menu/index.js +40 -0
  62. package/dist/editable/index.d.ts +53 -0
  63. package/dist/editable/index.js +162 -0
  64. package/dist/empty/index.d.ts +21 -0
  65. package/dist/empty/index.js +23 -0
  66. package/dist/flip/index.d.ts +49 -0
  67. package/dist/flip/index.js +138 -0
  68. package/dist/form/EditablePencilText.d.ts +13 -0
  69. package/dist/form/EditablePencilText.js +323 -0
  70. package/dist/form/Field.d.ts +13 -0
  71. package/dist/form/Field.js +26 -0
  72. package/dist/form/Input.d.ts +27 -0
  73. package/dist/form/Input.js +97 -0
  74. package/dist/form/InputAction.d.ts +7 -0
  75. package/dist/form/InputAction.js +24 -0
  76. package/dist/form/Textarea.d.ts +17 -0
  77. package/dist/form/Textarea.js +102 -0
  78. package/dist/form/context.d.ts +12 -0
  79. package/dist/form/context.js +10 -0
  80. package/dist/form/controlProps.d.ts +23 -0
  81. package/dist/form/controlProps.js +20 -0
  82. package/dist/form/index.d.ts +7 -0
  83. package/dist/form/index.js +3 -0
  84. package/dist/form/types.d.ts +6 -0
  85. package/dist/form/types.js +1 -0
  86. package/dist/heatmap/index.d.ts +59 -0
  87. package/dist/heatmap/index.js +248 -0
  88. package/dist/hotkey/index.d.ts +37 -0
  89. package/dist/hotkey/index.js +130 -0
  90. package/dist/hotkey/model.d.ts +27 -0
  91. package/dist/hotkey/model.js +97 -0
  92. package/dist/hover-card/index.d.ts +33 -0
  93. package/dist/hover-card/index.js +141 -0
  94. package/dist/image-cropper/index.d.ts +82 -0
  95. package/dist/image-cropper/index.js +261 -0
  96. package/dist/image-cropper/model.d.ts +42 -0
  97. package/dist/image-cropper/model.js +94 -0
  98. package/dist/image-viewer/index.d.ts +79 -0
  99. package/dist/image-viewer/index.js +220 -0
  100. package/dist/image-viewer/model.d.ts +9 -0
  101. package/dist/image-viewer/model.js +18 -0
  102. package/dist/images/layers-2x.png +0 -0
  103. package/dist/images/layers.png +0 -0
  104. package/dist/images/marker-icon.png +0 -0
  105. package/dist/index.d.ts +75 -0
  106. package/dist/index.js +69 -0
  107. package/dist/input-number/index.d.ts +16 -0
  108. package/dist/input-number/index.js +77 -0
  109. package/dist/internal/InputActionContext.d.ts +1 -0
  110. package/dist/internal/InputActionContext.js +3 -0
  111. package/dist/internal/cache.d.ts +14 -0
  112. package/dist/internal/cache.js +54 -0
  113. package/dist/internal/line.d.ts +4 -0
  114. package/dist/internal/line.js +21 -0
  115. package/dist/internal/paper.d.ts +1 -0
  116. package/dist/internal/paper.js +65 -0
  117. package/dist/internal/random.d.ts +3 -0
  118. package/dist/internal/random.js +30 -0
  119. package/dist/internal/recovery.d.ts +1 -0
  120. package/dist/internal/recovery.js +52 -0
  121. package/dist/internal/renderTokens.d.ts +10 -0
  122. package/dist/internal/renderTokens.js +13 -0
  123. package/dist/internal/stroke.d.ts +24 -0
  124. package/dist/internal/stroke.js +144 -0
  125. package/dist/internal/surfaceRenderer.d.ts +41 -0
  126. package/dist/internal/surfaceRenderer.js +178 -0
  127. package/dist/internal/textQueue.d.ts +1 -0
  128. package/dist/internal/textQueue.js +37 -0
  129. package/dist/internal/textRenderer.d.ts +28 -0
  130. package/dist/internal/textRenderer.js +239 -0
  131. package/dist/internal/travelIcons.d.ts +24 -0
  132. package/dist/internal/travelIcons.js +14 -0
  133. package/dist/internal/useBrowserLayoutEffect.d.ts +2 -0
  134. package/dist/internal/useBrowserLayoutEffect.js +3 -0
  135. package/dist/internal/usePencilSurface.d.ts +3 -0
  136. package/dist/internal/usePencilSurface.js +116 -0
  137. package/dist/internal/useSelectionDrawing.d.ts +2 -0
  138. package/dist/internal/useSelectionDrawing.js +90 -0
  139. package/dist/internal/wash.d.ts +16 -0
  140. package/dist/internal/wash.js +149 -0
  141. package/dist/journey-map/MapScale.d.ts +4 -0
  142. package/dist/journey-map/MapScale.js +54 -0
  143. package/dist/journey-map/index.d.ts +39 -0
  144. package/dist/journey-map/index.js +197 -0
  145. package/dist/journey-map/terrain.d.ts +8 -0
  146. package/dist/journey-map/terrain.js +127 -0
  147. package/dist/keypad/index.d.ts +23 -0
  148. package/dist/keypad/index.js +63 -0
  149. package/dist/keypad/model.d.ts +20 -0
  150. package/dist/keypad/model.js +33 -0
  151. package/dist/layout/index.d.ts +63 -0
  152. package/dist/layout/index.js +62 -0
  153. package/dist/log/index.d.ts +45 -0
  154. package/dist/log/index.js +159 -0
  155. package/dist/log/model.d.ts +16 -0
  156. package/dist/log/model.js +13 -0
  157. package/dist/node-graph/index.d.ts +123 -0
  158. package/dist/node-graph/index.js +304 -0
  159. package/dist/number-ticker/index.d.ts +29 -0
  160. package/dist/number-ticker/index.js +132 -0
  161. package/dist/number-ticker/model.d.ts +8 -0
  162. package/dist/number-ticker/model.js +34 -0
  163. package/dist/overflow-list/index.d.ts +39 -0
  164. package/dist/overflow-list/index.js +230 -0
  165. package/dist/overflow-list/layout.d.ts +19 -0
  166. package/dist/overflow-list/layout.js +44 -0
  167. package/dist/overlay/Overlay.d.ts +65 -0
  168. package/dist/overlay/Overlay.js +77 -0
  169. package/dist/overlay/Popover.d.ts +43 -0
  170. package/dist/overlay/Popover.js +45 -0
  171. package/dist/overlay/index.d.ts +4 -0
  172. package/dist/overlay/index.js +2 -0
  173. package/dist/overlay/themeBridge.d.ts +8 -0
  174. package/dist/overlay/themeBridge.js +98 -0
  175. package/dist/pagination/index.d.ts +23 -0
  176. package/dist/pagination/index.js +41 -0
  177. package/dist/pagination/model.d.ts +4 -0
  178. package/dist/pagination/model.js +42 -0
  179. package/dist/pan-zoom/index.d.ts +71 -0
  180. package/dist/pan-zoom/index.js +175 -0
  181. package/dist/pencil.d.ts +10 -0
  182. package/dist/pencil.js +39 -0
  183. package/dist/radar/index.d.ts +54 -0
  184. package/dist/radar/index.js +220 -0
  185. package/dist/range-slider/index.d.ts +16 -0
  186. package/dist/range-slider/index.js +22 -0
  187. package/dist/rating/index.d.ts +28 -0
  188. package/dist/rating/index.js +127 -0
  189. package/dist/resizable/index.d.ts +32 -0
  190. package/dist/resizable/index.js +50 -0
  191. package/dist/scroll-area/index.d.ts +36 -0
  192. package/dist/scroll-area/index.js +44 -0
  193. package/dist/segmented-progress/SegmentedProgress.d.ts +17 -0
  194. package/dist/segmented-progress/SegmentedProgress.js +42 -0
  195. package/dist/select/MultipleSelect.d.ts +44 -0
  196. package/dist/select/MultipleSelect.js +124 -0
  197. package/dist/select/SearchableSelect.d.ts +45 -0
  198. package/dist/select/SearchableSelect.js +50 -0
  199. package/dist/select/Select.d.ts +109 -0
  200. package/dist/select/Select.js +205 -0
  201. package/dist/select/index.d.ts +4 -0
  202. package/dist/select/index.js +2 -0
  203. package/dist/selection/Checkbox.d.ts +9 -0
  204. package/dist/selection/Checkbox.js +37 -0
  205. package/dist/selection/RadioGroup.d.ts +23 -0
  206. package/dist/selection/RadioGroup.js +82 -0
  207. package/dist/selection/Switch.d.ts +8 -0
  208. package/dist/selection/Switch.js +36 -0
  209. package/dist/selection/index.d.ts +6 -0
  210. package/dist/selection/index.js +3 -0
  211. package/dist/selection/useNativeChecked.d.ts +13 -0
  212. package/dist/selection/useNativeChecked.js +27 -0
  213. package/dist/slider/index.d.ts +11 -0
  214. package/dist/slider/index.js +45 -0
  215. package/dist/slot/index.d.ts +24 -0
  216. package/dist/slot/index.js +29 -0
  217. package/dist/sortable/index.d.ts +50 -0
  218. package/dist/sortable/index.js +278 -0
  219. package/dist/sortable/model.d.ts +30 -0
  220. package/dist/sortable/model.js +64 -0
  221. package/dist/sparkline/index.d.ts +19 -0
  222. package/dist/sparkline/index.js +240 -0
  223. package/dist/sparkline/model.d.ts +16 -0
  224. package/dist/sparkline/model.js +28 -0
  225. package/dist/steps/index.d.ts +16 -0
  226. package/dist/steps/index.js +37 -0
  227. package/dist/styles.css +8226 -0
  228. package/dist/tabs/index.d.ts +22 -0
  229. package/dist/tabs/index.js +42 -0
  230. package/dist/tag-input/index.d.ts +69 -0
  231. package/dist/tag-input/index.js +325 -0
  232. package/dist/tag-input/model.d.ts +14 -0
  233. package/dist/tag-input/model.js +38 -0
  234. package/dist/time-picker/index.d.ts +15 -0
  235. package/dist/time-picker/index.js +168 -0
  236. package/dist/timeline/index.d.ts +14 -0
  237. package/dist/timeline/index.js +16 -0
  238. package/dist/toast/index.d.ts +27 -0
  239. package/dist/toast/index.js +36 -0
  240. package/dist/toggle-group/index.d.ts +17 -0
  241. package/dist/toggle-group/index.js +26 -0
  242. package/dist/tokens.css +356 -0
  243. package/dist/tooltip/Tooltip.d.ts +24 -0
  244. package/dist/tooltip/Tooltip.js +32 -0
  245. package/dist/tooltip/index.d.ts +2 -0
  246. package/dist/tooltip/index.js +1 -0
  247. package/dist/transfer/index.d.ts +64 -0
  248. package/dist/transfer/index.js +174 -0
  249. package/dist/transfer/model.d.ts +12 -0
  250. package/dist/transfer/model.js +33 -0
  251. package/dist/tree/index.d.ts +39 -0
  252. package/dist/tree/index.js +36 -0
  253. package/dist/tree-select/index.d.ts +59 -0
  254. package/dist/tree-select/index.js +142 -0
  255. package/dist/tree-select/model.d.ts +31 -0
  256. package/dist/tree-select/model.js +135 -0
  257. package/dist/typewriter/index.d.ts +21 -0
  258. package/dist/typewriter/index.js +101 -0
  259. package/dist/typewriter/model.d.ts +7 -0
  260. package/dist/typewriter/model.js +45 -0
  261. package/dist/upload/index.d.ts +59 -0
  262. package/dist/upload/index.js +230 -0
  263. package/dist/virtual-list/index.d.ts +44 -0
  264. package/dist/virtual-list/index.js +128 -0
  265. package/dist/virtual-list/model.d.ts +7 -0
  266. package/dist/virtual-list/model.js +27 -0
  267. package/package.json +119 -4
@@ -0,0 +1,90 @@
1
+ import { useRef } from 'react';
2
+ import { useBrowserLayoutEffect } from './useBrowserLayoutEffect.js';
3
+ import { PENCIL_SELECTION_WASH } from './surfaceRenderer.js';
4
+ import { readRenderToken } from './renderTokens.js';
5
+ // Reveal the cached pigment in alternating diagonal strokes, without repainting it.
6
+ function drawingFrames(width, height, degrees) {
7
+ const angle = degrees * Math.PI / 180;
8
+ const cos = Math.cos(angle);
9
+ const sin = Math.sin(angle);
10
+ const u = (Math.abs(cos) * width + Math.abs(sin) * height) / 2 + 2;
11
+ const v = (Math.abs(sin) * width + Math.abs(cos) * height) / 2 + 2;
12
+ const point = (x, y) => `${width / 2 + cos * x - sin * y}px ${height / 2 + sin * x + cos * y}px`;
13
+ const frames = [];
14
+ const strokes = 8;
15
+ for (let index = 0; index < strokes; index++) {
16
+ const top = -v + index * 2 * v / strokes;
17
+ const bottom = -v + (index + 1) * 2 * v / strokes;
18
+ // Limit travel to the visible section of this stripe, not its rotated bounds.
19
+ const middle = (top + bottom) / 2;
20
+ let left = -u;
21
+ let right = u;
22
+ for (const [direction, offset, halfExtent] of [[cos, -sin * middle, width / 2], [sin, cos * middle, height / 2]]) {
23
+ if (Math.abs(direction) < 1e-8)
24
+ continue;
25
+ const a = (-halfExtent - offset) / direction;
26
+ const b = (halfExtent - offset) / direction;
27
+ left = Math.max(left, Math.min(a, b));
28
+ right = Math.min(right, Math.max(a, b));
29
+ }
30
+ for (const progress of [0, 0.5, 1]) {
31
+ const x = progress === 0 ? (index % 2 ? u : -u) : progress === 1 ? (index % 2 ? -u : u) : (left + right) / 2;
32
+ const outline = index % 2
33
+ ? [[-u, -v], [u, -v], [u, bottom], [x, bottom], [x, top], [-u, top]]
34
+ : [[-u, -v], [u, -v], [u, top], [x, top], [x, bottom], [-u, bottom]];
35
+ frames.push({ clipPath: `polygon(${outline.map(([a, b]) => point(a, b)).join(',')})`, offset: (index + progress * 0.85) / strokes, easing: progress === 1 ? 'steps(1, end)' : 'linear' });
36
+ }
37
+ }
38
+ frames.push({ ...frames[frames.length - 1], offset: 1 });
39
+ return frames;
40
+ }
41
+ export function useSelectionDrawing(ref, selected, animated = true) {
42
+ const previous = useRef(selected);
43
+ const progress = useRef(selected ? 1 : 0);
44
+ useBrowserLayoutEffect(() => {
45
+ const changed = selected !== previous.current;
46
+ previous.current = selected;
47
+ const node = ref.current;
48
+ const target = selected ? 1 : 0;
49
+ if (!animated || !changed || !node || typeof node.animate !== 'function') {
50
+ progress.current = target;
51
+ return;
52
+ }
53
+ const reduced = matchMedia('(prefers-reduced-motion: reduce)');
54
+ const forced = matchMedia('(forced-colors: active)');
55
+ if (reduced.matches || forced.matches || node.clientWidth < 6 || node.clientHeight < 6) {
56
+ progress.current = target;
57
+ return;
58
+ }
59
+ const duration = selected ? 480 : 320;
60
+ const angle = readRenderToken(getComputedStyle(node), '--inkra-wash-angle', PENCIL_SELECTION_WASH.angle, -85, 85);
61
+ const animation = node.animate(drawingFrames(node.clientWidth, node.clientHeight, angle).map(frame => ({ ...frame, opacity: 1 })), { duration, direction: selected ? 'normal' : 'reverse', easing: 'linear' });
62
+ animation.currentTime = (selected ? progress.current : 1 - progress.current) * duration;
63
+ let settled = false;
64
+ const finish = () => {
65
+ settled = true;
66
+ progress.current = target;
67
+ animation.cancel();
68
+ };
69
+ animation.onfinish = finish;
70
+ const width = node.clientWidth;
71
+ const height = node.clientHeight;
72
+ const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(() => {
73
+ if (node.clientWidth !== width || node.clientHeight !== height)
74
+ finish();
75
+ });
76
+ observer?.observe(node);
77
+ reduced.addEventListener('change', finish);
78
+ forced.addEventListener('change', finish);
79
+ return () => {
80
+ // Reverse from the visible stroke when users change their mind mid-draw.
81
+ if (!settled)
82
+ progress.current = animation.effect?.getComputedTiming().progress ?? target;
83
+ animation.onfinish = null;
84
+ animation.cancel();
85
+ observer?.disconnect();
86
+ reduced.removeEventListener('change', finish);
87
+ forced.removeEventListener('change', finish);
88
+ };
89
+ }, [ref, selected, animated]);
90
+ }
@@ -0,0 +1,16 @@
1
+ export interface PencilWashOptions {
2
+ strength?: number;
3
+ spacing?: number;
4
+ roughness?: number;
5
+ inset?: number;
6
+ radius?: number;
7
+ edgeReach?: number;
8
+ dpr?: number;
9
+ angle?: number;
10
+ }
11
+ export declare function drawPencilWash(context: CanvasRenderingContext2D, box: {
12
+ x: number;
13
+ y: number;
14
+ width: number;
15
+ height: number;
16
+ }, color: string, seed: number, options?: PencilWashOptions): void;
@@ -0,0 +1,149 @@
1
+ import { BoundedCache, PENCIL_RENDERER_VERSION, canvasByteCost, canvasIsUsable } from './cache.js';
2
+ import { pencilStroke } from './stroke.js';
3
+ const washCache = new BoundedCache(64, 32 * 1024 * 1024);
4
+ function randomAt(index, seed) {
5
+ let value = Math.imul(index ^ seed, 0x45d9f3b);
6
+ value = Math.imul(value ^ (value >>> 16), 0x45d9f3b);
7
+ return ((value ^ (value >>> 16)) >>> 0) / 4294967296;
8
+ }
9
+ function makeWash(width, height, color, seed, dpr, settings) {
10
+ const { strength, spacing, roughness, inset, radius, edgeReach, angle } = settings;
11
+ const bleed = edgeReach ? Math.ceil(edgeReach + 4) : 0;
12
+ const canvas = document.createElement('canvas');
13
+ canvas.width = Math.ceil((width + bleed * 2) * dpr);
14
+ canvas.height = Math.ceil((height + bleed * 2) * dpr);
15
+ const context = canvas.getContext('2d');
16
+ if (!context)
17
+ return canvas;
18
+ context.scale(dpr, dpr);
19
+ context.translate(bleed, bleed);
20
+ const margin = inset + spacing + roughness * 0.4;
21
+ const radians = angle * Math.PI / 180;
22
+ const cos = Math.cos(radians);
23
+ const sin = Math.sin(radians);
24
+ const halfWidth = Math.max(0, width / 2 - inset);
25
+ const halfHeight = Math.max(0, height / 2 - inset);
26
+ const projectedHeight = Math.abs(sin) * halfWidth * 2 + Math.abs(cos) * halfHeight * 2;
27
+ const rows = Math.max(1, Math.floor(((angle ? projectedHeight : height) - margin * 2) / spacing));
28
+ context.save();
29
+ if (angle) {
30
+ context.beginPath();
31
+ context.roundRect(inset, inset, halfWidth * 2, halfHeight * 2, Math.min(radius, halfWidth, halfHeight));
32
+ context.clip();
33
+ context.translate(width / 2, height / 2);
34
+ context.rotate(radians);
35
+ }
36
+ for (let row = 0; row <= rows; row += 1) {
37
+ const noise = (offset) => randomAt(row, seed + offset);
38
+ const y = margin + row * spacing - (angle ? projectedHeight / 2 : 0);
39
+ const edgeDistance = Math.max(0, Math.min(y - inset, height - inset - y));
40
+ const corner = edgeDistance < radius
41
+ ? radius - Math.sqrt(radius * radius - (radius - edgeDistance) ** 2) : 0;
42
+ const leftReach = edgeReach * (0.15 + noise(359) * 0.85);
43
+ const rightReach = edgeReach * (0.15 + noise(383) * 0.85);
44
+ let left = inset + corner + noise(31) * roughness * 1.8 - leftReach;
45
+ let right = width - inset - corner - noise(67) * roughness * 1.8 + rightReach;
46
+ if (angle) {
47
+ // Intersect each rotated stroke with the original box, so wide controls
48
+ // keep short diagonal marks rather than rotating their entire surface.
49
+ left = -Infinity;
50
+ right = Infinity;
51
+ for (const [direction, offset, halfExtent] of [[cos, -sin * y, halfWidth], [sin, cos * y, halfHeight]]) {
52
+ if (Math.abs(direction) < 1e-8)
53
+ continue;
54
+ const a = (-halfExtent - offset) / direction;
55
+ const b = (halfExtent - offset) / direction;
56
+ left = Math.max(left, Math.min(a, b));
57
+ right = Math.min(right, Math.max(a, b));
58
+ }
59
+ left += noise(31) * roughness * 1.8;
60
+ right -= noise(67) * roughness * 1.8;
61
+ }
62
+ if (right <= left)
63
+ continue;
64
+ const tilt = Math.min(4, width * 0.02) * (0.35 + noise(101) * 0.65);
65
+ const wave = Math.sin(row * 0.63 + seed % 19) * roughness * 0.24;
66
+ const edgeLift = edgeReach
67
+ ? (margin + edgeReach) * Math.max(0, 1 - edgeDistance / (radius + spacing * 3))
68
+ * (y < height / 2 ? -1 : 1)
69
+ : 0;
70
+ const points = [
71
+ [left, y + wave + edgeLift * noise(397)],
72
+ [left + (right - left) * 0.32, y - tilt * 0.3 + noise(149) - 0.5 + edgeLift * noise(401)],
73
+ [left + (right - left) * 0.7, y - tilt * 0.7 + noise(181) - 0.5 + edgeLift * noise(409)],
74
+ [right, y - tilt + wave + edgeLift * noise(419)],
75
+ ];
76
+ const feather = Math.min(1, (row + 1) / 3, (rows - row + 1) / 3);
77
+ context.globalAlpha = Math.min(1, strength * feather * (0.48 + noise(211) * 0.35));
78
+ pencilStroke(context, points, color, spacing * 1.55, seed + row * 337, 0.9, 1, false, {
79
+ variation: 0.94,
80
+ breaks: 0.28,
81
+ grain: 0.76,
82
+ gain: 2.1,
83
+ step: 2.2,
84
+ detail: 'fill',
85
+ taperLength: 5 + noise(257) * 8,
86
+ });
87
+ if (row % 4 === 1) {
88
+ context.globalAlpha = Math.min(1, strength * 0.28 * feather);
89
+ pencilStroke(context, points, color, 0.5, seed + row * 613, 0.65, 1, false, {
90
+ variation: 0.85,
91
+ breaks: 0.4,
92
+ grain: 0.8,
93
+ gain: 1.5,
94
+ step: 2.4,
95
+ detail: 'fill',
96
+ taperLength: 9,
97
+ });
98
+ }
99
+ }
100
+ context.restore();
101
+ context.globalCompositeOperation = 'destination-out';
102
+ const grains = Math.ceil((width + bleed * 2) * (height + bleed * 2) / 12);
103
+ for (let index = 0; index < grains; index += 1) {
104
+ const x = randomAt(index, seed + 811) * (width + bleed * 2) - bleed;
105
+ const y = randomAt(index, seed + 977) * (height + bleed * 2) - bleed;
106
+ const size = 0.35 + randomAt(index, seed + 1193) * 0.65;
107
+ context.globalAlpha = 0.2 + randomAt(index, seed + 1301) * 0.5;
108
+ context.fillRect(x, y, size, size * 0.6);
109
+ }
110
+ return canvas;
111
+ }
112
+ export function drawPencilWash(context, box, color, seed, options = {}) {
113
+ const width = Math.ceil(box.width);
114
+ const height = Math.ceil(box.height);
115
+ if (width < 1 || height < 1)
116
+ return;
117
+ const dpr = Math.max(1, Math.min(options.dpr ?? 1, 2));
118
+ const settings = {
119
+ strength: Math.max(0, options.strength ?? 1),
120
+ spacing: Math.max(1, options.spacing ?? 2),
121
+ roughness: Math.max(0, options.roughness ?? 3),
122
+ inset: Math.max(2, options.inset ?? 3),
123
+ radius: Math.max(0, Math.min(options.radius ?? 0, width / 2, height / 2)),
124
+ edgeReach: Math.max(0, Math.min(options.edgeReach ?? 0, width / 4, height / 4)),
125
+ angle: Number.isFinite(options.angle) ? Math.max(-85, Math.min(85, options.angle)) : 0,
126
+ };
127
+ const key = JSON.stringify([
128
+ PENCIL_RENDERER_VERSION,
129
+ width,
130
+ height,
131
+ color,
132
+ seed,
133
+ dpr,
134
+ settings,
135
+ ]);
136
+ let texture = washCache.get(key);
137
+ if (texture && !canvasIsUsable(texture)) {
138
+ washCache.delete(key);
139
+ texture = undefined;
140
+ }
141
+ if (!texture) {
142
+ texture = makeWash(width, height, color, seed, dpr, settings);
143
+ washCache.set(key, texture, canvasByteCost(texture));
144
+ }
145
+ const bleed = settings.edgeReach ? Math.ceil(settings.edgeReach + 4) : 0;
146
+ const scaleX = box.width / width;
147
+ const scaleY = box.height / height;
148
+ context.drawImage(texture, box.x - bleed * scaleX, box.y - bleed * scaleY, box.width + bleed * 2 * scaleX, box.height + bleed * 2 * scaleY);
149
+ }
@@ -0,0 +1,4 @@
1
+ import type { Map } from 'leaflet';
2
+ export declare function MapScale({ map }: {
3
+ map: Map | null;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,54 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { PencilText } from '../PencilText.js';
4
+ import { PencilIcon } from '../PencilIcon.js';
5
+ import { pencilStroke } from '../internal/stroke.js';
6
+ // AdventureMapScale's pencil bar and north marker, with Leaflet distance projection.
7
+ export function MapScale({ map }) {
8
+ const bar = useRef(null);
9
+ const [label, setLabel] = useState('');
10
+ useEffect(() => {
11
+ if (!map || !bar.current)
12
+ return;
13
+ const draw = () => {
14
+ const canvas = bar.current;
15
+ if (!canvas)
16
+ return;
17
+ const size = map.getSize();
18
+ if (!size.x || !size.y)
19
+ return;
20
+ const maxWidth = Math.min(112, Math.max(58, size.x * .22));
21
+ const y = size.y - 24, x = size.x - 64 - maxWidth;
22
+ const meters = map.distance(map.containerPointToLatLng([x, y]), map.containerPointToLatLng([x + maxWidth, y]));
23
+ if (!Number.isFinite(meters) || meters <= 0)
24
+ return;
25
+ const magnitude = 10 ** Math.floor(Math.log10(meters));
26
+ const distance = [5, 2, 1].map(value => value * magnitude).find(value => value <= meters) ?? magnitude;
27
+ const width = maxWidth * distance / meters;
28
+ setLabel(distance >= 1000 ? `${distance / 1000} km` : `${Number(distance.toPrecision(3))} m`);
29
+ const dpr = Math.min(devicePixelRatio || 1, 2);
30
+ canvas.width = Math.ceil((width + 4) * dpr);
31
+ canvas.height = 12 * dpr;
32
+ canvas.style.width = `${width + 4}px`;
33
+ const context = canvas.getContext('2d');
34
+ if (!context)
35
+ return;
36
+ context.scale(dpr, dpr);
37
+ const ink = getComputedStyle(canvas).color;
38
+ const options = { variation: .52, breaks: .18, grain: .45, gain: 2.2, step: .4, taperLength: .5 };
39
+ pencilStroke(context, [[2, 8], [width + 2, 8]], ink, 1.15, 4411, .16, 3, false, options);
40
+ pencilStroke(context, [[2, 3], [2, 10]], ink, 1.05, 4423, .14, 2, false, options);
41
+ pencilStroke(context, [[width + 2, 3], [width + 2, 10]], ink, 1.05, 4439, .14, 2, false, options);
42
+ };
43
+ draw();
44
+ map.on('moveend zoomend resize', draw);
45
+ const observer = new MutationObserver(draw);
46
+ for (let element = bar.current; element; element = element.parentElement) {
47
+ observer.observe(element, { attributes: true, attributeFilter: ['data-theme', 'data-inkra-theme'] });
48
+ }
49
+ const scheme = matchMedia('(prefers-color-scheme: dark)');
50
+ scheme.addEventListener('change', draw);
51
+ return () => { map.off('moveend zoomend resize', draw); observer.disconnect(); scheme.removeEventListener('change', draw); };
52
+ }, [map]);
53
+ return _jsxs("div", { className: "inkra-journey-map__orientation", "aria-hidden": "true", children: [_jsxs("div", { className: "inkra-journey-map__scale", children: [_jsx(PencilText, { children: label }), _jsx("canvas", { ref: bar })] }), _jsxs("div", { className: "inkra-journey-map__compass", children: [_jsx(PencilText, { children: "N" }), _jsx(PencilIcon, { sourceSize: 32, size: 30, paths: [{ d: 'M16 4 9.5 24 16 20.5 22.5 24 16 4Z' }, { d: 'M16 7v13.5' }] })] })] });
54
+ }
@@ -0,0 +1,39 @@
1
+ import { type HTMLAttributes } from 'react';
2
+ import type { GeoJsonObject } from 'geojson';
3
+ export interface JourneyPlace {
4
+ id: string;
5
+ label: string;
6
+ latitude: number;
7
+ longitude: number;
8
+ }
9
+ export interface JourneyRoute {
10
+ id: string;
11
+ coordinates: readonly (readonly [number, number])[];
12
+ }
13
+ export interface JourneyMapProps extends HTMLAttributes<HTMLDivElement> {
14
+ places: readonly JourneyPlace[];
15
+ /** [latitude, longitude]. Supplied geometry only; no routing or travel-time inference. */
16
+ routes?: readonly JourneyRoute[];
17
+ geography?: GeoJsonObject;
18
+ landCover?: GeoJsonObject;
19
+ water?: GeoJsonObject;
20
+ selectedPlace?: string;
21
+ onPlaceSelect?: (place: JourneyPlace) => void;
22
+ attribution: string;
23
+ attributionDisplay?: 'inline' | 'popover';
24
+ height?: number;
25
+ /** Controls viewport gestures, not place selection or explicit zoom buttons. */
26
+ interactive?: boolean;
27
+ scrollWheelZoom?: boolean;
28
+ showControls?: boolean;
29
+ showPlaceList?: boolean;
30
+ focusSelectedPlace?: boolean;
31
+ labels?: {
32
+ zoomIn: string;
33
+ zoomOut: string;
34
+ reset: string;
35
+ unavailable: string;
36
+ attribution?: string;
37
+ };
38
+ }
39
+ export declare function JourneyMap({ places, routes, geography, landCover, water, selectedPlace, onPlaceSelect, attribution, attributionDisplay, height, interactive, scrollWheelZoom, showControls, showPlaceList, focusSelectedPlace, labels, className, ...props }: JourneyMapProps): import("react").JSX.Element;
@@ -0,0 +1,197 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { createPortal } from 'react-dom';
4
+ import { Button } from '../Button.js';
5
+ import { Popover } from '../overlay/index.js';
6
+ import { PencilText } from '../PencilText.js';
7
+ import { pencilStroke } from '../internal/stroke.js';
8
+ import { TravelIcon } from '../internal/travelIcons.js';
9
+ import { paintTerrain, paintStop, paintSea } from './terrain.js';
10
+ import { MapScale } from './MapScale.js';
11
+ import { seedToNumber } from '../internal/random.js';
12
+ const defaults = { zoomIn: 'Zoom in', zoomOut: 'Zoom out', reset: 'Reset view', unavailable: 'Map unavailable. Choose a place below.' };
13
+ const noRoutes = [];
14
+ const valid = (place) => Number.isFinite(place.latitude) && Number.isFinite(place.longitude) && Math.abs(place.latitude) <= 85 && Math.abs(place.longitude) <= 180;
15
+ export function JourneyMap({ places, routes = noRoutes, geography, landCover, water, selectedPlace, onPlaceSelect, attribution, attributionDisplay = 'inline', height = 480, interactive = true, scrollWheelZoom = false, showControls = true, showPlaceList = false, focusSelectedPlace = false, labels = defaults, className, ...props }) {
16
+ const root = useRef(null);
17
+ const host = useRef(null);
18
+ const mapRef = useRef(null);
19
+ const fitRef = useRef(() => { });
20
+ const paintRef = useRef(() => { });
21
+ const fitZoomRef = useRef(2);
22
+ const selectionRef = useRef(onPlaceSelect);
23
+ selectionRef.current = onPlaceSelect;
24
+ const [ready, setReady] = useState(false);
25
+ const [failed, setFailed] = useState(false);
26
+ const [placeLabels, setPlaceLabels] = useState([]);
27
+ const selectedRef = useRef(selectedPlace);
28
+ selectedRef.current = selectedPlace;
29
+ const markersRef = useRef([]);
30
+ useEffect(() => {
31
+ let cancelled = false;
32
+ let cleanup;
33
+ setReady(false);
34
+ setFailed(false);
35
+ setPlaceLabels([]);
36
+ void import('leaflet').then(L => {
37
+ if (cancelled || !host.current)
38
+ return;
39
+ const map = L.map(host.current, { zoomControl: false, attributionControl: false, scrollWheelZoom: false, zoomSnap: .1, zoomAnimation: false, fadeAnimation: false, markerZoomAnimation: false }).setView([0, 0], 2);
40
+ mapRef.current = map;
41
+ cleanup = () => { map.remove(); mapRef.current = null; markersRef.current = []; };
42
+ const points = places.filter(valid);
43
+ const boundaries = [];
44
+ if (geography) {
45
+ const layer = L.geoJSON(geography);
46
+ const collect = (item) => {
47
+ if (item instanceof L.Polyline) {
48
+ const flatten = (values) => {
49
+ if (values[0] instanceof L.LatLng) {
50
+ const ring = values;
51
+ boundaries.push(item instanceof L.Polygon ? [...ring, ring[0]] : ring);
52
+ }
53
+ else
54
+ values.forEach(value => { if (Array.isArray(value))
55
+ flatten(value); });
56
+ };
57
+ flatten(item.getLatLngs());
58
+ }
59
+ else if (item instanceof L.LayerGroup)
60
+ item.eachLayer(collect);
61
+ };
62
+ layer.eachLayer(collect);
63
+ }
64
+ const routePoints = routes.map(route => route.coordinates.filter(([lat, lng]) => Number.isFinite(lat) && Number.isFinite(lng) && Math.abs(lat) <= 85 && Math.abs(lng) <= 180).map(([lat, lng]) => L.latLng(lat, lng)));
65
+ const all = [...points.map(place => L.latLng(place.latitude, place.longitude)), ...boundaries.flat(), ...routePoints.flat()];
66
+ let fitted = false;
67
+ const fit = () => {
68
+ map.invalidateSize();
69
+ const size = map.getSize();
70
+ if (size.x <= 0 || size.y <= 0)
71
+ return;
72
+ if (all.length)
73
+ map.fitBounds(L.latLngBounds(all), { padding: [44, 44], maxZoom: 12, animate: false });
74
+ fitZoomRef.current = map.getZoom();
75
+ fitted = true;
76
+ };
77
+ fitRef.current = fit;
78
+ const canvas = document.createElement('canvas');
79
+ canvas.className = 'inkra-journey-map__ink';
80
+ canvas.setAttribute('aria-hidden', 'true');
81
+ host.current.append(canvas);
82
+ let frame = 0;
83
+ let paintOrigin = map.containerPointToLatLng([0, 0]), paintZoom = map.getZoom();
84
+ const paint = () => {
85
+ frame = 0;
86
+ const { x: width, y: height } = map.getSize();
87
+ if (!width || !height)
88
+ return;
89
+ const dpr = Math.min(window.devicePixelRatio || 1, 2);
90
+ canvas.width = Math.round(width * dpr);
91
+ canvas.height = Math.round(height * dpr);
92
+ canvas.style.width = `${width}px`;
93
+ canvas.style.height = `${height}px`;
94
+ const context = canvas.getContext('2d');
95
+ if (!context)
96
+ return;
97
+ canvas.style.transform = '';
98
+ paintOrigin = map.containerPointToLatLng([0, 0]);
99
+ paintZoom = map.getZoom();
100
+ context.scale(dpr, dpr);
101
+ const style = getComputedStyle(host.current);
102
+ const paper = style.getPropertyValue('--inkra-paper').trim();
103
+ const dark = Number.parseInt(paper.replace('#', '').slice(0, 2), 16) < 100;
104
+ const project = (position) => { const point = map.latLngToContainerPoint([position[1], position[0]]); return [point.x, point.y]; };
105
+ paintSea(context, width, height, project([0, 0]));
106
+ if (geography)
107
+ paintTerrain(context, geography, project, width, height, dark);
108
+ if (landCover)
109
+ paintTerrain(context, landCover, project, width, height, dark);
110
+ if (water)
111
+ paintTerrain(context, water, project, width, height, dark, 'water');
112
+ const draw = (line, color, seed, weight) => {
113
+ const projected = line.map(point => { const p = map.latLngToContainerPoint(point); return [p.x, p.y]; });
114
+ pencilStroke(context, projected, color, weight, seed, .3, 3, false, { grain: .7, breaks: .15, variation: .8, gain: 2.5, step: .8 });
115
+ };
116
+ boundaries.forEach((line, i) => draw(line, style.getPropertyValue('--inkra-border-strong').trim(), i + 137, 1.2));
117
+ routePoints.forEach((line, i) => draw(line, style.getPropertyValue('--inkra-accent').trim(), i + 717, 2));
118
+ points.forEach(place => {
119
+ const p = map.latLngToContainerPoint([place.latitude, place.longitude]);
120
+ paintStop(context, p.x, p.y, seedToNumber(place.id), style.getPropertyValue('--inkra-accent-strong').trim(), paper, selectedRef.current === place.id);
121
+ });
122
+ };
123
+ const schedule = () => { if (!frame)
124
+ frame = requestAnimationFrame(paint); };
125
+ paintRef.current = schedule;
126
+ // Reuse the existing raster during a pan; repaint geographical detail on rest.
127
+ const translate = () => {
128
+ const point = map.latLngToContainerPoint(paintOrigin);
129
+ canvas.style.transformOrigin = '0 0';
130
+ canvas.style.transform = `translate(${point.x}px, ${point.y}px) scale(${map.getZoomScale(map.getZoom(), paintZoom)})`;
131
+ };
132
+ const settle = () => { canvas.style.transform = ''; paintOrigin = map.containerPointToLatLng([0, 0]); paintZoom = map.getZoom(); schedule(); };
133
+ map.on('move', translate);
134
+ map.on('moveend zoomend resize', settle);
135
+ map.on('dragstart', () => map.stop());
136
+ const labelNodes = [];
137
+ markersRef.current = points.map(place => {
138
+ const node = document.createElement('span');
139
+ node.className = 'inkra-journey-map__pin';
140
+ labelNodes.push({ place, node });
141
+ const marker = L.marker([place.latitude, place.longitude], { icon: L.divIcon({ html: node, className: 'inkra-journey-map__marker', iconSize: [44, 44], iconAnchor: [22, 22] }), keyboard: true, title: place.label, alt: place.label }).addTo(map);
142
+ marker.on('click', () => selectionRef.current?.(place));
143
+ marker.getElement()?.setAttribute('aria-pressed', String(selectedRef.current === place.id));
144
+ return { place, marker };
145
+ });
146
+ setPlaceLabels(labelNodes);
147
+ const observer = new ResizeObserver(() => { map.invalidateSize(); if (!fitted)
148
+ fit(); schedule(); });
149
+ observer.observe(host.current);
150
+ const appearance = new MutationObserver(schedule);
151
+ for (let element = host.current; element; element = element.parentElement) {
152
+ appearance.observe(element, { attributes: true, attributeFilter: ['data-theme', 'data-inkra-theme'] });
153
+ }
154
+ const scheme = matchMedia('(prefers-color-scheme: dark)');
155
+ scheme.addEventListener('change', schedule);
156
+ fit();
157
+ schedule();
158
+ setReady(true);
159
+ cleanup = () => { cancelAnimationFrame(frame); observer.disconnect(); appearance.disconnect(); scheme.removeEventListener('change', schedule); map.remove(); canvas.remove(); mapRef.current = null; markersRef.current = []; paintRef.current = () => { }; };
160
+ }).catch(() => { if (!cancelled) {
161
+ setFailed(true);
162
+ cleanup?.();
163
+ cleanup = undefined;
164
+ } });
165
+ return () => { cancelled = true; cleanup?.(); };
166
+ }, [places, routes, geography, landCover, water]);
167
+ useEffect(() => {
168
+ markersRef.current.forEach(({ place, marker }) => marker.getElement()?.setAttribute('aria-pressed', String(selectedPlace === place.id)));
169
+ paintRef.current();
170
+ const place = markersRef.current.find(item => item.place.id === selectedPlace)?.place;
171
+ if (focusSelectedPlace && place) {
172
+ const map = mapRef.current;
173
+ map?.stop();
174
+ const targetZoom = fitZoomRef.current + Math.log2(10);
175
+ if (matchMedia('(prefers-reduced-motion: reduce)').matches)
176
+ map?.setView([place.latitude, place.longitude], targetZoom, { animate: false });
177
+ else
178
+ map?.flyTo([place.latitude, place.longitude], targetZoom, { duration: .85 });
179
+ }
180
+ }, [selectedPlace, focusSelectedPlace, ready]);
181
+ useEffect(() => {
182
+ const map = mapRef.current;
183
+ if (!map || !ready)
184
+ return;
185
+ for (const handler of [map.dragging, map.touchZoom, map.doubleClickZoom, map.boxZoom, map.keyboard]) {
186
+ if (interactive)
187
+ handler.enable();
188
+ else
189
+ handler.disable();
190
+ }
191
+ if (interactive && scrollWheelZoom)
192
+ map.scrollWheelZoom.enable();
193
+ else
194
+ map.scrollWheelZoom.disable();
195
+ }, [interactive, scrollWheelZoom, ready]);
196
+ return _jsxs("div", { ...props, ref: root, className: ['inkra-journey-map', className].filter(Boolean).join(' '), children: [placeLabels.map(({ place, node }) => createPortal(_jsx(PencilText, { children: place.label }), node, place.id)), _jsxs("div", { className: "inkra-journey-map__viewport", style: { height: Math.max(240, Math.min(800, height)) }, children: [attributionDisplay === 'popover' && _jsx("div", { className: "inkra-journey-map__credits", children: _jsxs(Popover.Root, { children: [_jsx(Popover.Trigger, { asChild: true, children: _jsx(Button, { size: "icon", variant: "ghost", "aria-label": labels.attribution ?? 'Map credits', title: labels.attribution ?? 'Map credits', children: _jsx(TravelIcon, { name: "info" }) }) }), _jsx(Popover.Portal, { children: _jsxs(Popover.Content, { align: "end", sideOffset: 8, collisionPadding: 20, className: "inkra-journey-map__credits-content", children: [_jsx(Popover.Title, { children: labels.attribution ?? 'Map credits' }), _jsx(Popover.Description, { children: attribution })] }) })] }) }), _jsx("div", { ref: host, className: "inkra-journey-map__host", "aria-label": props['aria-label'] ?? 'Map' }), showControls && _jsxs("div", { className: "inkra-journey-map__tools", children: [_jsx(Button, { size: "icon", variant: "ghost", disabled: !ready, "aria-label": labels.zoomIn, title: labels.zoomIn, onClick: () => { mapRef.current?.stop(); mapRef.current?.zoomIn(); }, children: _jsx(TravelIcon, { name: "plus" }) }), _jsx(Button, { size: "icon", variant: "ghost", disabled: !ready, "aria-label": labels.zoomOut, title: labels.zoomOut, onClick: () => { mapRef.current?.stop(); mapRef.current?.zoomOut(); }, children: _jsx(TravelIcon, { name: "minus" }) }), _jsx(Button, { size: "icon", variant: "ghost", disabled: !ready, "aria-label": labels.reset, title: labels.reset, onClick: () => { mapRef.current?.stop(); fitRef.current(); }, children: _jsx(TravelIcon, { name: "reset" }) })] }), ready && _jsx(MapScale, { map: mapRef.current }), failed && _jsx("p", { role: "status", children: labels.unavailable })] }), attributionDisplay === 'inline' && _jsx("p", { className: "inkra-journey-map__attribution", children: attribution }), (showPlaceList || !ready || failed) && _jsx("ol", { className: "inkra-journey-map__places", children: places.filter(valid).map((place, index) => _jsx("li", { children: _jsxs("button", { type: "button", "aria-pressed": selectedPlace === place.id, onClick: () => { mapRef.current?.panTo([place.latitude, place.longitude], { animate: false }); onPlaceSelect?.(place); }, children: [_jsx("span", { "aria-hidden": "true", children: index + 1 }), _jsx(PencilText, { children: place.label })] }) }, place.id)) })] });
197
+ }
@@ -0,0 +1,8 @@
1
+ import type { GeoJsonObject, Feature, Position } from 'geojson';
2
+ import { type PencilPoint } from '../internal/stroke.js';
3
+ type Project = (position: Position) => PencilPoint;
4
+ export declare function paintSea(ctx: CanvasRenderingContext2D, width: number, height: number, anchor: PencilPoint): void;
5
+ export declare function mapFeatures(data: GeoJsonObject): Feature[];
6
+ export declare function paintTerrain(ctx: CanvasRenderingContext2D, data: GeoJsonObject, project: Project, width: number, height: number, dark: boolean, defaultKind?: string): void;
7
+ export declare function paintStop(ctx: CanvasRenderingContext2D, x: number, y: number, seed: number, color: string, paper: string, selected: boolean): void;
8
+ export {};