@inkra/react 0.0.0-stage → 0.1.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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +213 -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 +212 -0
  9. package/dist/PencilLine.d.ts +10 -0
  10. package/dist/PencilLine.js +85 -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 +205 -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/assets/ui-hand-yozai.woff2 +0 -0
  20. package/dist/breadcrumb/index.d.ts +16 -0
  21. package/dist/breadcrumb/index.js +25 -0
  22. package/dist/calendar/index.d.ts +21 -0
  23. package/dist/calendar/index.js +41 -0
  24. package/dist/content/index.d.ts +106 -0
  25. package/dist/content/index.js +80 -0
  26. package/dist/day-agenda/index.d.ts +43 -0
  27. package/dist/day-agenda/index.js +47 -0
  28. package/dist/day-agenda/layout.d.ts +20 -0
  29. package/dist/day-agenda/layout.js +60 -0
  30. package/dist/display/index.d.ts +32 -0
  31. package/dist/display/index.js +59 -0
  32. package/dist/dropdown-menu/index.d.ts +34 -0
  33. package/dist/dropdown-menu/index.js +40 -0
  34. package/dist/form/EditablePencilText.d.ts +13 -0
  35. package/dist/form/EditablePencilText.js +312 -0
  36. package/dist/form/Field.d.ts +13 -0
  37. package/dist/form/Field.js +26 -0
  38. package/dist/form/Input.d.ts +27 -0
  39. package/dist/form/Input.js +97 -0
  40. package/dist/form/InputAction.d.ts +7 -0
  41. package/dist/form/InputAction.js +24 -0
  42. package/dist/form/Textarea.d.ts +5 -0
  43. package/dist/form/Textarea.js +23 -0
  44. package/dist/form/context.d.ts +12 -0
  45. package/dist/form/context.js +10 -0
  46. package/dist/form/controlProps.d.ts +23 -0
  47. package/dist/form/controlProps.js +20 -0
  48. package/dist/form/index.d.ts +7 -0
  49. package/dist/form/index.js +3 -0
  50. package/dist/form/types.d.ts +6 -0
  51. package/dist/form/types.js +1 -0
  52. package/dist/images/layers-2x.png +0 -0
  53. package/dist/images/layers.png +0 -0
  54. package/dist/images/marker-icon.png +0 -0
  55. package/dist/index.d.ts +28 -0
  56. package/dist/index.js +22 -0
  57. package/dist/input-number/index.d.ts +16 -0
  58. package/dist/input-number/index.js +77 -0
  59. package/dist/internal/InputActionContext.d.ts +1 -0
  60. package/dist/internal/InputActionContext.js +3 -0
  61. package/dist/internal/cache.d.ts +14 -0
  62. package/dist/internal/cache.js +54 -0
  63. package/dist/internal/line.d.ts +4 -0
  64. package/dist/internal/line.js +21 -0
  65. package/dist/internal/paper.d.ts +1 -0
  66. package/dist/internal/paper.js +65 -0
  67. package/dist/internal/random.d.ts +3 -0
  68. package/dist/internal/random.js +30 -0
  69. package/dist/internal/recovery.d.ts +1 -0
  70. package/dist/internal/recovery.js +52 -0
  71. package/dist/internal/stroke.d.ts +24 -0
  72. package/dist/internal/stroke.js +144 -0
  73. package/dist/internal/surfaceRenderer.d.ts +33 -0
  74. package/dist/internal/surfaceRenderer.js +175 -0
  75. package/dist/internal/textQueue.d.ts +1 -0
  76. package/dist/internal/textQueue.js +37 -0
  77. package/dist/internal/textRenderer.d.ts +27 -0
  78. package/dist/internal/textRenderer.js +213 -0
  79. package/dist/internal/travelIcons.d.ts +24 -0
  80. package/dist/internal/travelIcons.js +14 -0
  81. package/dist/internal/useBrowserLayoutEffect.d.ts +2 -0
  82. package/dist/internal/useBrowserLayoutEffect.js +3 -0
  83. package/dist/internal/usePencilSurface.d.ts +3 -0
  84. package/dist/internal/usePencilSurface.js +108 -0
  85. package/dist/internal/useSelectionDrawing.d.ts +2 -0
  86. package/dist/internal/useSelectionDrawing.js +86 -0
  87. package/dist/internal/wash.d.ts +16 -0
  88. package/dist/internal/wash.js +149 -0
  89. package/dist/journey-map/MapScale.d.ts +4 -0
  90. package/dist/journey-map/MapScale.js +52 -0
  91. package/dist/journey-map/index.d.ts +39 -0
  92. package/dist/journey-map/index.js +195 -0
  93. package/dist/journey-map/terrain.d.ts +8 -0
  94. package/dist/journey-map/terrain.js +127 -0
  95. package/dist/overlay/Overlay.d.ts +65 -0
  96. package/dist/overlay/Overlay.js +77 -0
  97. package/dist/overlay/Popover.d.ts +43 -0
  98. package/dist/overlay/Popover.js +45 -0
  99. package/dist/overlay/index.d.ts +4 -0
  100. package/dist/overlay/index.js +2 -0
  101. package/dist/overlay/themeBridge.d.ts +8 -0
  102. package/dist/overlay/themeBridge.js +98 -0
  103. package/dist/pencil.d.ts +10 -0
  104. package/dist/pencil.js +39 -0
  105. package/dist/select/MultipleSelect.d.ts +44 -0
  106. package/dist/select/MultipleSelect.js +124 -0
  107. package/dist/select/SearchableSelect.d.ts +45 -0
  108. package/dist/select/SearchableSelect.js +50 -0
  109. package/dist/select/Select.d.ts +109 -0
  110. package/dist/select/Select.js +205 -0
  111. package/dist/select/index.d.ts +4 -0
  112. package/dist/select/index.js +2 -0
  113. package/dist/selection/Checkbox.d.ts +9 -0
  114. package/dist/selection/Checkbox.js +37 -0
  115. package/dist/selection/RadioGroup.d.ts +23 -0
  116. package/dist/selection/RadioGroup.js +82 -0
  117. package/dist/selection/Switch.d.ts +8 -0
  118. package/dist/selection/Switch.js +36 -0
  119. package/dist/selection/index.d.ts +6 -0
  120. package/dist/selection/index.js +3 -0
  121. package/dist/selection/useNativeChecked.d.ts +13 -0
  122. package/dist/selection/useNativeChecked.js +27 -0
  123. package/dist/slider/index.d.ts +11 -0
  124. package/dist/slider/index.js +45 -0
  125. package/dist/styles.css +3663 -0
  126. package/dist/tabs/index.d.ts +22 -0
  127. package/dist/tabs/index.js +42 -0
  128. package/dist/tokens.css +355 -0
  129. package/dist/tooltip/Tooltip.d.ts +24 -0
  130. package/dist/tooltip/Tooltip.js +32 -0
  131. package/dist/tooltip/index.d.ts +2 -0
  132. package/dist/tooltip/index.js +1 -0
  133. package/package.json +100 -4
@@ -0,0 +1,3 @@
1
+ import type { RefObject } from 'react';
2
+ import type { PencilSurfaceVariant } from './surfaceRenderer.js';
3
+ export declare function usePencilSurface(rootRef: RefObject<HTMLElement | null>, canvasRef: RefObject<HTMLCanvasElement | null>, borderRef: RefObject<HTMLCanvasElement | null>, variant: PencilSurfaceVariant, seed: string | number, clipContent: boolean, radius?: number): void;
@@ -0,0 +1,108 @@
1
+ import { useBrowserLayoutEffect } from './useBrowserLayoutEffect.js';
2
+ import { PENCIL_RENDERER_VERSION, effectiveDpr } from './cache.js';
3
+ import { observeCanvasRecovery } from './recovery.js';
4
+ import { seedToNumber } from './random.js';
5
+ import { drawPencilSurface } from './surfaceRenderer.js';
6
+ function token(style, name, fallback) {
7
+ return style.getPropertyValue(name).trim() || fallback;
8
+ }
9
+ export function usePencilSurface(rootRef, canvasRef, borderRef, variant, seed, clipContent, radius) {
10
+ useBrowserLayoutEffect(() => {
11
+ const root = rootRef.current;
12
+ const canvas = canvasRef.current;
13
+ if (!root || !canvas)
14
+ return undefined;
15
+ let frame = 0;
16
+ let lastPaint = '';
17
+ let disposed = false;
18
+ const paint = () => {
19
+ frame = 0;
20
+ if (disposed)
21
+ return;
22
+ const width = root.clientWidth;
23
+ const height = root.clientHeight;
24
+ if (width < 6 || height < 6)
25
+ return;
26
+ const dpr = effectiveDpr(2);
27
+ const style = getComputedStyle(root);
28
+ const paper = token(style, '--inkra-paper', '#fcfcfa');
29
+ const ink = variant === 'action' || variant === 'outline'
30
+ ? token(style, '--inkra-accent-strong', '#234e39')
31
+ : variant === 'danger'
32
+ ? token(style, '--inkra-danger', '#873e36')
33
+ : token(style, '--inkra-border-strong', '#778575');
34
+ const dark = style.colorScheme.includes('dark');
35
+ const wash = variant === 'selection'
36
+ ? token(style, '--inkra-selection-pigment', dark ? '#c6b873' : '#769566')
37
+ : variant === 'danger'
38
+ ? token(style, '--inkra-danger-soft', '#d49b8b')
39
+ : variant === 'badge'
40
+ ? token(style, '--inkra-coral-soft', '#f8ebe7')
41
+ : variant === 'quiet'
42
+ ? token(style, '--inkra-accent-soft', '#e6efe8')
43
+ : token(style, '--inkra-accent', '#2e634a');
44
+ const numericSeed = seedToNumber(seed);
45
+ const paintKey = JSON.stringify([
46
+ PENCIL_RENDERER_VERSION,
47
+ variant,
48
+ numericSeed,
49
+ width,
50
+ height,
51
+ dpr,
52
+ paper,
53
+ ink,
54
+ wash,
55
+ dark,
56
+ clipContent,
57
+ radius,
58
+ ]);
59
+ if (paintKey === lastPaint && !canvas.getContext('2d')?.isContextLost?.())
60
+ return;
61
+ try {
62
+ const points = drawPencilSurface(canvas, borderRef.current, width, height, variant, numericSeed, dpr, { paper, dark, ink, wash }, radius);
63
+ if (clipContent && points) {
64
+ root.style.setProperty('--inkra-pencil-clip', `polygon(${points.map(([x, y]) => `${x / width * 100}% ${y / height * 100}%`).join(',')})`);
65
+ }
66
+ else {
67
+ root.style.removeProperty('--inkra-pencil-clip');
68
+ }
69
+ delete root.dataset.pencilFallback;
70
+ lastPaint = paintKey;
71
+ }
72
+ catch {
73
+ root.dataset.pencilFallback = 'true';
74
+ root.style.removeProperty('--inkra-pencil-clip');
75
+ lastPaint = '';
76
+ }
77
+ };
78
+ const schedule = () => {
79
+ if (!frame)
80
+ frame = requestAnimationFrame(paint);
81
+ };
82
+ const resize = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(schedule);
83
+ resize?.observe(root);
84
+ window.addEventListener('resize', schedule, { passive: true });
85
+ const appearance = new MutationObserver(schedule);
86
+ appearance.observe(document.documentElement, {
87
+ attributes: true,
88
+ subtree: true,
89
+ attributeFilter: ['data-theme', 'data-inkra-theme'],
90
+ });
91
+ const colorScheme = window.matchMedia('(prefers-color-scheme: dark)');
92
+ colorScheme.addEventListener('change', schedule);
93
+ const stopRecovery = observeCanvasRecovery(() => {
94
+ lastPaint = '';
95
+ schedule();
96
+ });
97
+ paint();
98
+ return () => {
99
+ disposed = true;
100
+ stopRecovery();
101
+ resize?.disconnect();
102
+ appearance.disconnect();
103
+ colorScheme.removeEventListener('change', schedule);
104
+ window.removeEventListener('resize', schedule);
105
+ cancelAnimationFrame(frame);
106
+ };
107
+ }, [variant, seed, clipContent, radius, rootRef, canvasRef, borderRef]);
108
+ }
@@ -0,0 +1,2 @@
1
+ import { type RefObject } from 'react';
2
+ export declare function useSelectionDrawing(ref: RefObject<HTMLElement | null>, selected: boolean, animated?: boolean): void;
@@ -0,0 +1,86 @@
1
+ import { useRef } from 'react';
2
+ import { useBrowserLayoutEffect } from './useBrowserLayoutEffect.js';
3
+ import { PENCIL_SELECTION_WASH } from './surfaceRenderer.js';
4
+ // Reveal the cached pigment in alternating diagonal strokes, without repainting it.
5
+ function drawingFrames(width, height) {
6
+ const angle = PENCIL_SELECTION_WASH.angle * Math.PI / 180;
7
+ const cos = Math.cos(angle);
8
+ const sin = Math.sin(angle);
9
+ const u = (Math.abs(cos) * width + Math.abs(sin) * height) / 2 + 2;
10
+ const v = (Math.abs(sin) * width + Math.abs(cos) * height) / 2 + 2;
11
+ const point = (x, y) => `${width / 2 + cos * x - sin * y}px ${height / 2 + sin * x + cos * y}px`;
12
+ const frames = [];
13
+ const strokes = 8;
14
+ for (let index = 0; index < strokes; index++) {
15
+ const top = -v + index * 2 * v / strokes;
16
+ const bottom = -v + (index + 1) * 2 * v / strokes;
17
+ // Limit travel to the visible section of this stripe, not its rotated bounds.
18
+ const middle = (top + bottom) / 2;
19
+ const ends = [
20
+ (-width / 2 + sin * middle) / cos,
21
+ (width / 2 + sin * middle) / cos,
22
+ (-height / 2 - cos * middle) / sin,
23
+ (height / 2 - cos * middle) / sin,
24
+ ];
25
+ const left = Math.max(Math.min(ends[0], ends[1]), Math.min(ends[2], ends[3]));
26
+ const right = Math.min(Math.max(ends[0], ends[1]), Math.max(ends[2], ends[3]));
27
+ for (const progress of [0, 0.5, 1]) {
28
+ const x = progress === 0 ? (index % 2 ? u : -u) : progress === 1 ? (index % 2 ? -u : u) : (left + right) / 2;
29
+ const outline = index % 2
30
+ ? [[-u, -v], [u, -v], [u, bottom], [x, bottom], [x, top], [-u, top]]
31
+ : [[-u, -v], [u, -v], [u, top], [x, top], [x, bottom], [-u, bottom]];
32
+ 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' });
33
+ }
34
+ }
35
+ frames.push({ ...frames[frames.length - 1], offset: 1 });
36
+ return frames;
37
+ }
38
+ export function useSelectionDrawing(ref, selected, animated = true) {
39
+ const previous = useRef(selected);
40
+ const progress = useRef(selected ? 1 : 0);
41
+ useBrowserLayoutEffect(() => {
42
+ const changed = selected !== previous.current;
43
+ previous.current = selected;
44
+ const node = ref.current;
45
+ const target = selected ? 1 : 0;
46
+ if (!animated || !changed || !node || typeof node.animate !== 'function') {
47
+ progress.current = target;
48
+ return;
49
+ }
50
+ const reduced = matchMedia('(prefers-reduced-motion: reduce)');
51
+ const forced = matchMedia('(forced-colors: active)');
52
+ if (reduced.matches || forced.matches || node.clientWidth < 6 || node.clientHeight < 6) {
53
+ progress.current = target;
54
+ return;
55
+ }
56
+ const duration = selected ? 480 : 320;
57
+ const animation = node.animate(drawingFrames(node.clientWidth, node.clientHeight).map(frame => ({ ...frame, opacity: 1 })), { duration, direction: selected ? 'normal' : 'reverse', easing: 'linear' });
58
+ animation.currentTime = (selected ? progress.current : 1 - progress.current) * duration;
59
+ let settled = false;
60
+ const finish = () => {
61
+ settled = true;
62
+ progress.current = target;
63
+ animation.cancel();
64
+ };
65
+ animation.onfinish = finish;
66
+ const width = node.clientWidth;
67
+ const height = node.clientHeight;
68
+ const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(() => {
69
+ if (node.clientWidth !== width || node.clientHeight !== height)
70
+ finish();
71
+ });
72
+ observer?.observe(node);
73
+ reduced.addEventListener('change', finish);
74
+ forced.addEventListener('change', finish);
75
+ return () => {
76
+ // Reverse from the visible stroke when users change their mind mid-draw.
77
+ if (!settled)
78
+ progress.current = animation.effect?.getComputedTiming().progress ?? target;
79
+ animation.onfinish = null;
80
+ animation.cancel();
81
+ observer?.disconnect();
82
+ reduced.removeEventListener('change', finish);
83
+ forced.removeEventListener('change', finish);
84
+ };
85
+ }, [ref, selected, animated]);
86
+ }
@@ -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,52 @@
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
+ observer.observe(document.documentElement, { subtree: true, attributes: true, attributeFilter: ['data-theme', 'data-inkra-theme'] });
47
+ const scheme = matchMedia('(prefers-color-scheme: dark)');
48
+ scheme.addEventListener('change', draw);
49
+ return () => { map.off('moveend zoomend resize', draw); observer.disconnect(); scheme.removeEventListener('change', draw); };
50
+ }, [map]);
51
+ 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' }] })] })] });
52
+ }
@@ -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;