@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,178 @@
1
+ import { pencilPaper } from './paper.js';
2
+ import { pencilStroke } from './stroke.js';
3
+ import { drawPencilWash } from './wash.js';
4
+ import { pencilRenderDefaults } from './renderTokens.js';
5
+ export const PENCIL_ACTION_WASH = Object.freeze({
6
+ lightStrength: 0.2,
7
+ darkStrength: 0.12,
8
+ });
9
+ export const PENCIL_QUIET_WASH = Object.freeze({
10
+ lightStrength: 0.12,
11
+ darkStrength: 0.08,
12
+ });
13
+ const surfaceStyles = {
14
+ paper: { radius: 8, width: 1.2, passes: 3 },
15
+ action: {
16
+ radius: 7,
17
+ width: 1.35,
18
+ passes: 3,
19
+ themeStrength: { light: PENCIL_ACTION_WASH.lightStrength, dark: PENCIL_ACTION_WASH.darkStrength },
20
+ },
21
+ quiet: {
22
+ radius: 7,
23
+ width: 0.75,
24
+ passes: 2,
25
+ themeStrength: { light: PENCIL_QUIET_WASH.lightStrength, dark: PENCIL_QUIET_WASH.darkStrength },
26
+ },
27
+ badge: { radius: 6, width: 0.9, passes: 2, strength: 0.25 },
28
+ outline: { radius: 7, width: 1.2, passes: 3 },
29
+ danger: { radius: 7, width: 0.75, passes: 2, strength: 0.18 },
30
+ };
31
+ export const PENCIL_FILL_WASH = Object.freeze({
32
+ angle: pencilRenderDefaults.washAngle,
33
+ spacing: pencilRenderDefaults.washSpacing,
34
+ roughness: pencilRenderDefaults.washRoughness,
35
+ inset: 2,
36
+ edgeReach: 0,
37
+ });
38
+ export const PENCIL_SELECTION_WASH = Object.freeze({
39
+ ...PENCIL_FILL_WASH,
40
+ lightStrength: PENCIL_ACTION_WASH.lightStrength,
41
+ darkStrength: PENCIL_ACTION_WASH.darkStrength,
42
+ radius: 8,
43
+ });
44
+ function outline(width, height, radius, seed) {
45
+ const left = 2.5;
46
+ const top = 2.5;
47
+ const right = width - 2.5;
48
+ const bottom = height - 2.5;
49
+ const points = [];
50
+ for (let corner = 0; corner < 4; corner += 1) {
51
+ const variation = radius === 0 ? 0 : ((seed >>> (corner * 4)) & 7) * 0.12;
52
+ const resolvedRadius = Math.min(radius + variation, (right - left) / 2, (bottom - top) / 2);
53
+ const centerX = corner < 2 ? right - resolvedRadius : left + resolvedRadius;
54
+ const centerY = corner === 0 || corner === 3 ? top + resolvedRadius : bottom - resolvedRadius;
55
+ for (let step = 0; step <= 6; step += 1) {
56
+ const angle = (corner - 1 + step / 6) * Math.PI / 2;
57
+ points.push([
58
+ centerX + Math.cos(angle) * resolvedRadius,
59
+ centerY + Math.sin(angle) * resolvedRadius,
60
+ ]);
61
+ }
62
+ }
63
+ return points;
64
+ }
65
+ function paperPattern(context, color, dark, dpr) {
66
+ const pattern = context.createPattern(pencilPaper(color, dark, dpr), 'repeat');
67
+ if (!pattern)
68
+ return color;
69
+ if (typeof pattern.setTransform === 'function' && typeof DOMMatrix !== 'undefined') {
70
+ pattern.setTransform(new DOMMatrix().scale(1 / dpr));
71
+ }
72
+ return pattern;
73
+ }
74
+ export function drawPencilSurface(canvas, borderCanvas, width, height, variant, seed, dpr, theme, radius) {
75
+ const bleed = variant === 'wash' ? 12 : 0;
76
+ canvas.width = Math.ceil((width + bleed * 2) * dpr);
77
+ canvas.height = Math.ceil((height + bleed * 2) * dpr);
78
+ const context = canvas.getContext('2d');
79
+ if (!context)
80
+ throw new Error('Canvas 2D is unavailable');
81
+ context.setTransform(canvas.width / (width + bleed * 2), 0, 0, canvas.height / (height + bleed * 2), 0, 0);
82
+ if (variant === 'wash') {
83
+ if (borderCanvas) {
84
+ borderCanvas.width = canvas.width;
85
+ borderCanvas.height = canvas.height;
86
+ }
87
+ context.translate(bleed, bleed);
88
+ drawPencilWash(context, { x: 0, y: 0, width, height }, theme.wash, seed, {
89
+ strength: theme.washSettings?.strength ?? pencilRenderDefaults.washStrength / 0.48,
90
+ spacing: theme.washSettings?.spacing ?? pencilRenderDefaults.washSpacing,
91
+ roughness: theme.washSettings?.roughness ?? pencilRenderDefaults.washRoughness,
92
+ angle: (theme.washSettings?.angle ?? pencilRenderDefaults.washAngle) + 35,
93
+ inset: 2,
94
+ radius: 8,
95
+ edgeReach: 8,
96
+ dpr,
97
+ });
98
+ return undefined;
99
+ }
100
+ if (variant === 'selection') {
101
+ if (borderCanvas) {
102
+ borderCanvas.width = canvas.width;
103
+ borderCanvas.height = canvas.height;
104
+ }
105
+ drawPencilWash(context, { x: 0, y: 0, width, height }, theme.wash, seed, {
106
+ strength: (theme.dark ? PENCIL_SELECTION_WASH.darkStrength : PENCIL_SELECTION_WASH.lightStrength) * (theme.washSettings?.strength ?? pencilRenderDefaults.washStrength / 0.48),
107
+ spacing: theme.washSettings?.spacing ?? PENCIL_SELECTION_WASH.spacing,
108
+ roughness: theme.washSettings?.roughness ?? PENCIL_SELECTION_WASH.roughness,
109
+ inset: PENCIL_SELECTION_WASH.inset,
110
+ radius: PENCIL_SELECTION_WASH.radius,
111
+ edgeReach: PENCIL_SELECTION_WASH.edgeReach,
112
+ angle: theme.washSettings?.angle ?? PENCIL_SELECTION_WASH.angle,
113
+ dpr,
114
+ });
115
+ return undefined;
116
+ }
117
+ if (variant === 'ghost') {
118
+ if (borderCanvas) {
119
+ borderCanvas.width = canvas.width;
120
+ borderCanvas.height = canvas.height;
121
+ }
122
+ return undefined;
123
+ }
124
+ if (variant === 'full') {
125
+ if (borderCanvas) {
126
+ borderCanvas.width = canvas.width;
127
+ borderCanvas.height = canvas.height;
128
+ }
129
+ context.fillStyle = paperPattern(context, theme.paper, theme.dark, dpr);
130
+ context.fillRect(0, 0, width, height);
131
+ return undefined;
132
+ }
133
+ const style = surfaceStyles[variant];
134
+ const strength = style.themeStrength
135
+ ? (theme.dark ? style.themeStrength.dark : style.themeStrength.light)
136
+ : style.strength;
137
+ const resolvedRadius = radius !== undefined && Number.isFinite(radius) ? Math.max(0, Math.min(radius, 8)) : style.radius;
138
+ const points = outline(width, height, resolvedRadius, seed);
139
+ if (variant !== 'outline') {
140
+ context.save();
141
+ context.beginPath();
142
+ context.moveTo(...points[0]);
143
+ for (const point of points.slice(1))
144
+ context.lineTo(...point);
145
+ context.closePath();
146
+ context.clip();
147
+ context.fillStyle = paperPattern(context, theme.paper, theme.dark, dpr);
148
+ context.fillRect(0, 0, width, height);
149
+ if (strength) {
150
+ drawPencilWash(context, { x: 0, y: 0, width, height }, theme.wash, seed, {
151
+ ...PENCIL_FILL_WASH,
152
+ ...theme.washSettings,
153
+ strength: strength * (theme.washSettings?.strength ?? pencilRenderDefaults.washStrength / 0.48),
154
+ radius: resolvedRadius,
155
+ dpr,
156
+ });
157
+ }
158
+ context.restore();
159
+ }
160
+ let borderContext = context;
161
+ if (borderCanvas) {
162
+ borderCanvas.width = canvas.width;
163
+ borderCanvas.height = canvas.height;
164
+ const separateContext = borderCanvas.getContext('2d');
165
+ if (separateContext) {
166
+ separateContext.setTransform(canvas.width / width, 0, 0, canvas.height / height, 0, 0);
167
+ borderContext = separateContext;
168
+ }
169
+ }
170
+ pencilStroke(borderContext, points, theme.ink, style.width * ((theme.strokeWidth ?? pencilRenderDefaults.strokeWidth) / 1.2), seed, theme.strokeRoughness ?? pencilRenderDefaults.strokeRoughness, style.passes, true, {
171
+ variation: 0.82,
172
+ breaks: variant === 'paper' ? 0.2 : 0.25,
173
+ grain: 0.7,
174
+ gain: variant === 'action' ? 2.5 : 2.1,
175
+ step: 0.8,
176
+ });
177
+ return points;
178
+ }
@@ -0,0 +1 @@
1
+ export declare function queueTextPaint(job: () => void): () => void;
@@ -0,0 +1,37 @@
1
+ const jobs = new Set();
2
+ let pending = null;
3
+ function schedule() {
4
+ if (pending !== null || !jobs.size)
5
+ return;
6
+ const run = (deadline) => {
7
+ pending = null;
8
+ const start = performance.now();
9
+ do {
10
+ const job = jobs.values().next().value;
11
+ if (!job)
12
+ break;
13
+ jobs.delete(job);
14
+ job();
15
+ } while (jobs.size && performance.now() - start < 6 && (deadline?.timeRemaining() ?? 3) > 2);
16
+ schedule();
17
+ };
18
+ const idleWindow = window;
19
+ pending = idleWindow.requestIdleCallback
20
+ ? idleWindow.requestIdleCallback(run, { timeout: 300 })
21
+ : window.setTimeout(run, 16);
22
+ }
23
+ export function queueTextPaint(job) {
24
+ jobs.add(job);
25
+ schedule();
26
+ return () => {
27
+ jobs.delete(job);
28
+ if (!jobs.size && pending !== null) {
29
+ const idleWindow = window;
30
+ if (idleWindow.cancelIdleCallback)
31
+ idleWindow.cancelIdleCallback(pending);
32
+ else
33
+ window.clearTimeout(pending);
34
+ pending = null;
35
+ }
36
+ };
37
+ }
@@ -0,0 +1,28 @@
1
+ export interface PencilLetteringSettings {
2
+ variation: number;
3
+ breaks: number;
4
+ outline: number;
5
+ fill: number;
6
+ grain: number;
7
+ }
8
+ export interface PencilTextSprite {
9
+ ink: HTMLCanvasElement;
10
+ width: number;
11
+ height: number;
12
+ advance: number;
13
+ ascent: number;
14
+ descent: number;
15
+ padding: number;
16
+ }
17
+ export declare const pencilLettering: PencilLetteringSettings;
18
+ export declare const pencilHandwriting = "\"Trip Map Hand\",\"HanziPen SC\",\"Hannotate SC\",\"Kaiti SC\",\"STKaiti\",cursive";
19
+ export declare function renderPencilText(text: string, options: {
20
+ size: number;
21
+ family?: string;
22
+ color: string;
23
+ seed: number;
24
+ dpr: number;
25
+ settings?: PencilLetteringSettings;
26
+ cache?: boolean;
27
+ glyphSeed?: number;
28
+ }): PencilTextSprite;
@@ -0,0 +1,239 @@
1
+ import { BoundedCache, PENCIL_RENDERER_VERSION, canvasByteCost, canvasIsUsable } from './cache.js';
2
+ import { seededRandom } from './random.js';
3
+ export const pencilLettering = Object.freeze({
4
+ variation: 0.2,
5
+ breaks: 0.5,
6
+ outline: 0.9,
7
+ fill: 0.9,
8
+ grain: 0.6,
9
+ });
10
+ export const pencilHandwriting = '"Trip Map Hand","HanziPen SC","Hannotate SC","Kaiti SC","STKaiti",cursive';
11
+ const TEXT_PIXEL_LIMIT = 524288;
12
+ const textCache = new BoundedCache(160, 16 * 1024 * 1024);
13
+ const glyphCache = new BoundedCache(512, 8 * 1024 * 1024);
14
+ const clamp = (value) => Math.max(0, Math.min(1, value));
15
+ function hash(x, y, seed) {
16
+ let value = Math.imul(x ^ seed, 374761393) ^ Math.imul(y, 668265263);
17
+ value = Math.imul(value ^ (value >>> 13), 1274126177);
18
+ return ((value ^ (value >>> 16)) >>> 0) / 4294967296;
19
+ }
20
+ function field(x, y, seed) {
21
+ const integerX = Math.floor(x);
22
+ const integerY = Math.floor(y);
23
+ const fractionX = x - integerX;
24
+ const fractionY = y - integerY;
25
+ const smoothX = fractionX * fractionX * (3 - 2 * fractionX);
26
+ const smoothY = fractionY * fractionY * (3 - 2 * fractionY);
27
+ return (hash(integerX, integerY, seed) * (1 - smoothX)
28
+ + hash(integerX + 1, integerY, seed) * smoothX) * (1 - smoothY)
29
+ + (hash(integerX, integerY + 1, seed) * (1 - smoothX)
30
+ + hash(integerX + 1, integerY + 1, seed) * smoothX) * smoothY;
31
+ }
32
+ function distanceTo(mask, width, height, inside) {
33
+ const distance = new Float32Array(width * height);
34
+ for (let index = 0; index < distance.length; index += 1) {
35
+ distance[index] = (mask[index * 4 + 3] > 100) === inside ? 0 : 1000;
36
+ }
37
+ const relax = (index, neighbor, cost) => {
38
+ distance[index] = Math.min(distance[index], distance[neighbor] + cost);
39
+ };
40
+ for (let y = 0; y < height; y += 1) {
41
+ for (let x = 0; x < width; x += 1) {
42
+ const index = y * width + x;
43
+ if (x)
44
+ relax(index, index - 1, 1);
45
+ if (y) {
46
+ relax(index, index - width, 1);
47
+ if (x)
48
+ relax(index, index - width - 1, 1.414);
49
+ if (x + 1 < width)
50
+ relax(index, index - width + 1, 1.414);
51
+ }
52
+ }
53
+ }
54
+ for (let y = height - 1; y >= 0; y -= 1) {
55
+ for (let x = width - 1; x >= 0; x -= 1) {
56
+ const index = y * width + x;
57
+ if (x + 1 < width)
58
+ relax(index, index + 1, 1);
59
+ if (y + 1 < height) {
60
+ relax(index, index + width, 1);
61
+ if (x)
62
+ relax(index, index + width - 1, 1.414);
63
+ if (x + 1 < width)
64
+ relax(index, index + width + 1, 1.414);
65
+ }
66
+ }
67
+ }
68
+ return distance;
69
+ }
70
+ export function renderPencilText(text, options) {
71
+ const { size, color, seed, dpr } = options;
72
+ const family = options.family ?? pencilHandwriting;
73
+ const settings = options.settings ?? pencilLettering;
74
+ const cache = options.cache ?? true;
75
+ if (text.length > 256 || !Number.isFinite(size) || size <= 0 || size > 128) {
76
+ throw new RangeError('Pencil text exceeds the bounded renderer input');
77
+ }
78
+ const resolution = Math.max(2, Math.min(4, Math.ceil(dpr * 2)));
79
+ const font = `400 ${size}px ${family}`;
80
+ const fontReady = document.fonts?.check(font, text) ?? false;
81
+ const key = cache ? JSON.stringify([
82
+ PENCIL_RENDERER_VERSION,
83
+ text,
84
+ size,
85
+ family,
86
+ color,
87
+ seed,
88
+ dpr,
89
+ resolution,
90
+ settings,
91
+ fontReady,
92
+ options.glyphSeed,
93
+ ]) : '';
94
+ if (cache) {
95
+ const cached = textCache.get(key);
96
+ if (cached && canvasIsUsable(cached.ink))
97
+ return cached;
98
+ }
99
+ const mask = document.createElement('canvas');
100
+ const measure = mask.getContext('2d', { willReadFrequently: true });
101
+ if (!measure)
102
+ throw new Error('Canvas 2D is unavailable');
103
+ measure.fillStyle = color;
104
+ measure.fillRect(0, 0, 1, 1);
105
+ const rgb = measure.getImageData(0, 0, 1, 1).data;
106
+ measure.font = font;
107
+ measure.fontKerning = 'none';
108
+ const characters = Array.from(text);
109
+ const widths = characters.map(character => measure.measureText(character).width);
110
+ const advance = Math.ceil(widths.reduce((sum, width) => sum + width, 0));
111
+ const padding = 5;
112
+ const ascent = Math.ceil(size * 1.1);
113
+ const descent = Math.ceil(size * 0.35);
114
+ const width = advance + padding * 2;
115
+ const height = ascent + descent + padding * 2;
116
+ if (width * height * resolution ** 2 > TEXT_PIXEL_LIMIT) {
117
+ throw new RangeError('Pencil text exceeds the bounded pixel budget');
118
+ }
119
+ mask.width = width * resolution;
120
+ mask.height = height * resolution;
121
+ const context = mask.getContext('2d', { willReadFrequently: true });
122
+ if (!context)
123
+ throw new Error('Canvas 2D is unavailable');
124
+ context.scale(resolution, resolution);
125
+ // Plain Latin labels can reuse bounded character sprites. Complex scripts and
126
+ // private editable text retain the original whole-line rendering path.
127
+ if (cache && options.glyphSeed !== undefined && /^[\x20-\x7e]+$/.test(text)) {
128
+ let left = 0;
129
+ characters.forEach((character, index) => {
130
+ if (character !== ' ') {
131
+ const characterSeed = (options.glyphSeed ^ Math.imul(character.charCodeAt(0), 16777619)
132
+ ^ Math.imul(index % 3, 374761393)) >>> 0;
133
+ const glyphKey = JSON.stringify([
134
+ PENCIL_RENDERER_VERSION, character, size, family, color, characterSeed,
135
+ resolution, settings, document.fonts?.check(font, character),
136
+ ]);
137
+ let glyph = glyphCache.get(glyphKey);
138
+ if (!glyph || !canvasIsUsable(glyph.ink)) {
139
+ glyph = renderPencilText(character, { ...options, seed: characterSeed, glyphSeed: undefined, cache: false });
140
+ glyphCache.set(glyphKey, glyph, canvasByteCost(glyph.ink));
141
+ }
142
+ context.drawImage(glyph.ink, left, 0, glyph.width, glyph.height);
143
+ }
144
+ left += widths[index];
145
+ });
146
+ const sprite = { ink: mask, width, height, advance, ascent, descent, padding };
147
+ return textCache.set(key, sprite, canvasByteCost(mask));
148
+ }
149
+ context.font = font;
150
+ context.fontKerning = 'none';
151
+ context.fillStyle = color;
152
+ context.strokeStyle = color;
153
+ context.lineWidth = size >= 16 ? 0.48 : 0.24;
154
+ const random = seededRandom(seed);
155
+ let x = padding;
156
+ characters.forEach((character, index) => {
157
+ const center = x + widths[index] / 2;
158
+ const baseline = padding + ascent + (random() - 0.5) * 0.55;
159
+ context.save();
160
+ context.translate(center, baseline);
161
+ context.rotate((random() - 0.5) * 0.035);
162
+ context.globalAlpha = 1;
163
+ context.fillText(character, -widths[index] / 2, 0);
164
+ context.strokeText(character, -widths[index] / 2, 0);
165
+ context.restore();
166
+ x += widths[index];
167
+ });
168
+ const image = context.getImageData(0, 0, mask.width, mask.height);
169
+ const shape = new Uint8ClampedArray(image.data);
170
+ const outside = distanceTo(shape, mask.width, mask.height, true);
171
+ const inside = distanceTo(shape, mask.width, mask.height, false);
172
+ const cuts = [];
173
+ let characterLeft = padding;
174
+ for (let character = 0; character < characters.length; character += 1) {
175
+ for (let attempt = 0, found = 0; attempt < 80 && found < 2; attempt += 1) {
176
+ const centerX = characterLeft + random() * widths[character];
177
+ const centerY = padding + ascent - size + random() * size;
178
+ const pixel = Math.floor(centerY * resolution) * mask.width + Math.floor(centerX * resolution);
179
+ if (shape[pixel * 4 + 3] < 130)
180
+ continue;
181
+ const angle = random() * Math.PI;
182
+ cuts.push({
183
+ x: centerX,
184
+ y: centerY,
185
+ cosine: Math.cos(angle),
186
+ sine: Math.sin(angle),
187
+ long: 1.1 + random() * 0.7,
188
+ short: 0.22 + random() * 0.2,
189
+ });
190
+ found += 1;
191
+ }
192
+ characterLeft += widths[character];
193
+ }
194
+ const columnCuts = Array.from({ length: mask.width }, (_, column) => cuts.filter(cut => Math.abs(column / resolution - cut.x)
195
+ <= Math.hypot(cut.cosine * cut.long, cut.sine * cut.short) * 1.55));
196
+ for (let y = 0; y < mask.height; y += 1) {
197
+ for (let xIndex = 0; xIndex < mask.width; xIndex += 1) {
198
+ const index = (y * mask.width + xIndex) * 4;
199
+ const pixel = y * mask.width + xIndex;
200
+ if (outside[pixel] > resolution * 1.1) {
201
+ image.data[index + 3] = 0;
202
+ continue;
203
+ }
204
+ const pixelX = xIndex / resolution;
205
+ const pixelY = y / resolution;
206
+ const force = field(pixelX / 2.7, pixelY / 2.4, seed + 31);
207
+ const pressure = 1 - settings.variation * 0.94
208
+ * (1 - clamp((force - 0.23) / 0.46) ** 0.85);
209
+ const paperTooth = field(pixelX * 2.3, pixelY * 2.1, seed + 113);
210
+ const tooth = 1 - settings.grain * 0.76
211
+ * (1 - clamp((paperTooth - 0.2) / 0.53));
212
+ let contact = 1;
213
+ for (const cut of columnCuts[xIndex]) {
214
+ const deltaX = pixelX - cut.x;
215
+ const deltaY = pixelY - cut.y;
216
+ const radius = Math.hypot((deltaX * cut.cosine + deltaY * cut.sine) / cut.long, (-deltaX * cut.sine + deltaY * cut.cosine) / cut.short);
217
+ contact = Math.min(contact, clamp((radius - 0.85) / 0.7));
218
+ }
219
+ const distance = shape[index + 3] > 100
220
+ ? -(inside[pixel] - 0.5) / resolution
221
+ : (outside[pixel] - 0.5) / resolution;
222
+ const edgeWidth = 0.34 + settings.outline * 0.5;
223
+ const edgeWobble = (field(pixelX * 0.9, pixelY * 0.9, seed + 209) - 0.5)
224
+ * 0.32 * settings.outline;
225
+ const rim = clamp(1 - Math.abs(distance + edgeWobble) / edgeWidth);
226
+ const core = (shape[index + 3] / 255) * (0.28 + 0.64 * settings.fill);
227
+ const lift = 1 - settings.breaks * 1.75 * (1 - contact);
228
+ const pigment = clamp(Math.max(core, rim * (0.48 + 0.52 * settings.outline))
229
+ * pressure * tooth * Math.max(0.08, lift) * 1.38);
230
+ image.data[index] = rgb[0];
231
+ image.data[index + 1] = rgb[1];
232
+ image.data[index + 2] = rgb[2];
233
+ image.data[index + 3] = Math.round(255 * pigment);
234
+ }
235
+ }
236
+ context.putImageData(image, 0, 0);
237
+ const sprite = { ink: mask, width, height, advance, ascent, descent, padding };
238
+ return cache ? textCache.set(key, sprite, canvasByteCost(mask)) : sprite;
239
+ }
@@ -0,0 +1,24 @@
1
+ declare const paths: {
2
+ plus: {
3
+ d: string;
4
+ }[];
5
+ minus: {
6
+ d: string;
7
+ }[];
8
+ reset: {
9
+ d: string;
10
+ }[];
11
+ previous: {
12
+ d: string;
13
+ }[];
14
+ next: {
15
+ d: string;
16
+ }[];
17
+ info: {
18
+ d: string;
19
+ }[];
20
+ };
21
+ export declare function TravelIcon({ name }: {
22
+ name: keyof typeof paths;
23
+ }): import("react").JSX.Element;
24
+ export {};
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { PencilIcon } from '../PencilIcon.js';
3
+ // Lucide ISC geometry rendered through the same pencil renderer as the trip tools.
4
+ const paths = {
5
+ plus: [{ d: 'M5 12h14' }, { d: 'M12 5v14' }],
6
+ minus: [{ d: 'M5 12h14' }],
7
+ reset: [{ d: 'M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8' }, { d: 'M3 3v5h5' }],
8
+ previous: [{ d: 'm12 19-7-7 7-7' }, { d: 'M19 12H5' }],
9
+ next: [{ d: 'M5 12h14' }, { d: 'm12 5 7 7-7 7' }],
10
+ info: [{ d: 'M22 12a10 10 0 1 1-20 0 10 10 0 0 1 20 0' }, { d: 'M12 16v-4' }, { d: 'M12 8h.01' }],
11
+ };
12
+ export function TravelIcon({ name }) {
13
+ return _jsx(PencilIcon, { paths: paths[name], sourceSize: 24, size: 30 });
14
+ }
@@ -0,0 +1,2 @@
1
+ import { useEffect } from 'react';
2
+ export declare const useBrowserLayoutEffect: typeof useEffect;
@@ -0,0 +1,3 @@
1
+ import { useEffect, useLayoutEffect } from 'react';
2
+ // Layout and canvas work only runs after the server markup reaches a browser.
3
+ export const useBrowserLayoutEffect = typeof document === 'undefined' ? useEffect : useLayoutEffect;
@@ -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,116 @@
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
+ import { maxStrokeRoughness, pencilRenderDefaults, readRenderToken } from './renderTokens.js';
7
+ function token(style, name, fallback) {
8
+ return style.getPropertyValue(name).trim() || fallback;
9
+ }
10
+ export function usePencilSurface(rootRef, canvasRef, borderRef, variant, seed, clipContent, radius) {
11
+ useBrowserLayoutEffect(() => {
12
+ const root = rootRef.current;
13
+ const canvas = canvasRef.current;
14
+ if (!root || !canvas)
15
+ return undefined;
16
+ let frame = 0;
17
+ let lastPaint = '';
18
+ let disposed = false;
19
+ const paint = () => {
20
+ frame = 0;
21
+ if (disposed)
22
+ return;
23
+ const width = root.clientWidth;
24
+ const height = root.clientHeight;
25
+ if (width < 6 || height < 6)
26
+ return;
27
+ const dpr = effectiveDpr(2);
28
+ const style = getComputedStyle(root);
29
+ const paper = token(style, '--inkra-paper', '#fcfcfa');
30
+ const ink = variant === 'action' || variant === 'outline'
31
+ ? token(style, '--inkra-accent-strong', '#234e39')
32
+ : variant === 'danger'
33
+ ? token(style, '--inkra-danger', '#873e36')
34
+ : token(style, '--inkra-border-strong', '#778575');
35
+ const dark = style.colorScheme.includes('dark');
36
+ const strokeWidth = readRenderToken(style, '--inkra-stroke-width', pencilRenderDefaults.strokeWidth, 0.3, 3);
37
+ const strokeRoughness = readRenderToken(style, '--inkra-stroke-amplitude', pencilRenderDefaults.strokeRoughness, 0, maxStrokeRoughness);
38
+ const washSettings = {
39
+ strength: readRenderToken(style, '--inkra-wash-strength', pencilRenderDefaults.washStrength, 0, 1) / 0.48,
40
+ spacing: readRenderToken(style, '--inkra-wash-spacing', pencilRenderDefaults.washSpacing, 1, 6),
41
+ roughness: readRenderToken(style, '--inkra-wash-roughness', pencilRenderDefaults.washRoughness, 0, 6),
42
+ angle: readRenderToken(style, '--inkra-wash-angle', pencilRenderDefaults.washAngle, -85, 85),
43
+ };
44
+ const wash = variant === 'selection'
45
+ ? token(style, '--inkra-selection-pigment', dark ? '#c6b873' : '#769566')
46
+ : variant === 'danger'
47
+ ? token(style, '--inkra-danger-soft', '#d49b8b')
48
+ : variant === 'badge'
49
+ ? token(style, '--inkra-coral-soft', '#f8ebe7')
50
+ : variant === 'quiet'
51
+ ? token(style, '--inkra-accent-soft', '#e6efe8')
52
+ : token(style, '--inkra-accent', '#2e634a');
53
+ const numericSeed = seedToNumber(seed);
54
+ const paintKey = JSON.stringify([
55
+ PENCIL_RENDERER_VERSION,
56
+ variant,
57
+ numericSeed,
58
+ width,
59
+ height,
60
+ dpr,
61
+ paper,
62
+ ink,
63
+ wash,
64
+ dark,
65
+ clipContent,
66
+ radius,
67
+ strokeWidth, strokeRoughness, washSettings,
68
+ ]);
69
+ if (paintKey === lastPaint && !canvas.getContext('2d')?.isContextLost?.())
70
+ return;
71
+ try {
72
+ const points = drawPencilSurface(canvas, borderRef.current, width, height, variant, numericSeed, dpr, { paper, dark, ink, wash, strokeWidth, strokeRoughness, washSettings }, radius);
73
+ if (clipContent && points) {
74
+ root.style.setProperty('--inkra-pencil-clip', `polygon(${points.map(([x, y]) => `${x / width * 100}% ${y / height * 100}%`).join(',')})`);
75
+ }
76
+ else {
77
+ root.style.removeProperty('--inkra-pencil-clip');
78
+ }
79
+ delete root.dataset.pencilFallback;
80
+ lastPaint = paintKey;
81
+ }
82
+ catch {
83
+ root.dataset.pencilFallback = 'true';
84
+ root.style.removeProperty('--inkra-pencil-clip');
85
+ lastPaint = '';
86
+ }
87
+ };
88
+ const schedule = () => {
89
+ if (!frame)
90
+ frame = requestAnimationFrame(paint);
91
+ };
92
+ const resize = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(schedule);
93
+ resize?.observe(root);
94
+ window.addEventListener('resize', schedule, { passive: true });
95
+ const colorScheme = window.matchMedia('(prefers-color-scheme: dark)');
96
+ const inheritedStyle = new MutationObserver(schedule);
97
+ for (let element = root; element; element = element.parentElement) {
98
+ inheritedStyle.observe(element, { attributes: true, attributeFilter: ['style', 'class', 'data-theme', 'data-inkra-theme'] });
99
+ }
100
+ colorScheme.addEventListener('change', schedule);
101
+ const stopRecovery = observeCanvasRecovery(() => {
102
+ lastPaint = '';
103
+ schedule();
104
+ });
105
+ paint();
106
+ return () => {
107
+ disposed = true;
108
+ stopRecovery();
109
+ resize?.disconnect();
110
+ inheritedStyle.disconnect();
111
+ colorScheme.removeEventListener('change', schedule);
112
+ window.removeEventListener('resize', schedule);
113
+ cancelAnimationFrame(frame);
114
+ };
115
+ }, [variant, seed, clipContent, radius, rootRef, canvasRef, borderRef]);
116
+ }
@@ -0,0 +1,2 @@
1
+ import { type RefObject } from 'react';
2
+ export declare function useSelectionDrawing(ref: RefObject<HTMLElement | null>, selected: boolean, animated?: boolean): void;