ranger-compiler 3.5.1 → 3.5.2

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 (338) hide show
  1. package/CHANGELOG.md +1064 -20
  2. package/LICENSE +3 -1
  3. package/README.md +123 -28
  4. package/dist/Lang.rgr +1188 -290
  5. package/dist/api.d.ts +2343 -879
  6. package/dist/api.js +79923 -56921
  7. package/dist/lib/JSON.rgr +102 -91
  8. package/dist/lib/Shell.rgr +4 -4
  9. package/dist/lib/apple/AppleToolchain.rgr +4 -4
  10. package/dist/lib/apple/README.md +5 -4
  11. package/dist/lib/apple/apple_test.rgr +1 -1
  12. package/dist/lib/core/README.md +1 -1
  13. package/dist/lib/evg/EVG.rgr +12 -0
  14. package/dist/lib/evg/EVGA11yFromTree.rgr +302 -0
  15. package/dist/lib/evg/EVGA11yTree.rgr +894 -0
  16. package/dist/lib/evg/EVGBox.rgr +267 -0
  17. package/dist/lib/evg/EVGBoxShorthandTest.rgr +220 -0
  18. package/dist/lib/evg/EVGCodepoint.rgr +316 -0
  19. package/dist/lib/evg/EVGColor.rgr +700 -0
  20. package/dist/lib/evg/EVGCommands.rgr +177 -0
  21. package/dist/lib/evg/EVGComponent.rgr +331 -0
  22. package/dist/lib/evg/EVGComponentTest.rgr +387 -0
  23. package/dist/lib/evg/EVGConnector.rgr +541 -0
  24. package/dist/lib/evg/EVGConnectorTest.rgr +306 -0
  25. package/dist/lib/evg/EVGDisplayList.rgr +4186 -0
  26. package/dist/lib/evg/EVGEasing.rgr +370 -0
  27. package/dist/lib/evg/EVGEffectTest.rgr +253 -0
  28. package/dist/lib/evg/EVGElement.rgr +4914 -0
  29. package/dist/lib/evg/EVGFixedTest.rgr +289 -0
  30. package/dist/lib/evg/EVGFlexRulesTest.rgr +317 -0
  31. package/dist/lib/evg/EVGFlexWrapTest.rgr +246 -0
  32. package/dist/lib/evg/EVGFling.rgr +244 -0
  33. package/dist/lib/evg/EVGFocus.rgr +400 -0
  34. package/dist/lib/evg/EVGFocusTest.rgr +399 -0
  35. package/dist/lib/evg/EVGGradient.rgr +322 -0
  36. package/dist/lib/evg/EVGGrapheme.rgr +190 -0
  37. package/dist/lib/evg/EVGGrid.rgr +975 -0
  38. package/dist/lib/evg/EVGHitTest.rgr +208 -0
  39. package/dist/lib/evg/EVGHoles.rgr +294 -0
  40. package/dist/lib/evg/EVGHostMeasurerTest.rgr +281 -0
  41. package/dist/lib/evg/EVGHostTextMeasurer.rgr +331 -0
  42. package/dist/lib/evg/EVGHostTree.rgr +834 -0
  43. package/dist/lib/evg/EVGHostTreeTest.rgr +447 -0
  44. package/dist/lib/evg/EVGImageDecode.rgr +117 -0
  45. package/dist/lib/evg/EVGImageMeasurer.rgr +86 -0
  46. package/dist/lib/evg/EVGInspect.rgr +883 -0
  47. package/dist/lib/evg/EVGInvalidateTest.rgr +432 -0
  48. package/dist/lib/evg/EVGJsonTest.rgr +535 -0
  49. package/dist/lib/evg/EVGLayout.rgr +4220 -0
  50. package/dist/lib/evg/EVGMeasure.rgr +1267 -0
  51. package/dist/lib/evg/EVGOverlayTest.rgr +626 -0
  52. package/dist/lib/evg/EVGPatch.rgr +1721 -0
  53. package/dist/lib/evg/EVGPatchTest.rgr +692 -0
  54. package/dist/lib/evg/EVGPopoverTest.rgr +395 -0
  55. package/dist/lib/evg/EVGReconcile.rgr +226 -0
  56. package/dist/lib/evg/EVGReconcileTest.rgr +421 -0
  57. package/dist/lib/evg/EVGReject.rgr +126 -0
  58. package/dist/lib/evg/EVGRelayoutTest.rgr +375 -0
  59. package/dist/lib/evg/EVGRtlLayoutTest.rgr +319 -0
  60. package/dist/lib/evg/EVGRuler.rgr +232 -0
  61. package/dist/lib/evg/EVGRulerTest.rgr +172 -0
  62. package/dist/lib/evg/EVGSelectChrome.rgr +178 -0
  63. package/dist/lib/evg/EVGStyleCacheTest.rgr +419 -0
  64. package/dist/lib/evg/EVGStyleSheet.rgr +2148 -0
  65. package/dist/lib/evg/EVGStyleStateTest.rgr +407 -0
  66. package/dist/lib/evg/EVGStyleVarTest.rgr +471 -0
  67. package/dist/lib/evg/EVGText.rgr +105 -0
  68. package/dist/lib/evg/EVGTextEngine.rgr +536 -0
  69. package/dist/lib/evg/EVGTextMeasurer.rgr +720 -0
  70. package/dist/lib/evg/EVGTimingTest.rgr +1417 -0
  71. package/dist/lib/evg/EVGToolbar.rgr +1369 -0
  72. package/dist/lib/evg/EVGTransition.rgr +728 -0
  73. package/dist/lib/evg/EVGTreeJson.rgr +721 -0
  74. package/dist/lib/evg/EVGUnit.rgr +554 -0
  75. package/dist/lib/evg/EVGViewportUnitTest.rgr +236 -0
  76. package/dist/lib/evg/EvgApp.rgr +189 -0
  77. package/dist/lib/evg/EvgBitmapTracer.rgr +4277 -0
  78. package/dist/lib/evg/EvgBitmapTracerTest.rgr +2119 -0
  79. package/dist/lib/evg/EvgHost.rgr +451 -0
  80. package/dist/lib/evg/EvgTest.rgr +91 -0
  81. package/dist/lib/evg/EvgTraceColor.rgr +236 -0
  82. package/dist/lib/evg/EvgTraceCurve.rgr +648 -0
  83. package/dist/lib/evg/EvgTraceFit.rgr +767 -0
  84. package/dist/lib/evg/EvgTracePath.rgr +435 -0
  85. package/dist/lib/evg/EvgTraceTypes.rgr +559 -0
  86. package/dist/lib/evg/EvgViewport.rgr +313 -0
  87. package/dist/lib/evg/FxDemoDoc.rgr +185 -0
  88. package/dist/lib/evg/HOSTS.md +227 -0
  89. package/dist/lib/evg/ISSUES.md +835 -0
  90. package/dist/lib/evg/PLAN_ACCESSIBILITY.md +650 -0
  91. package/dist/lib/evg/PLAN_CSS_LAYOUT_AND_FONTS.md +1124 -0
  92. package/dist/lib/evg/PLAN_EFFECTS.md +431 -0
  93. package/dist/lib/evg/PLAN_EVG.md +518 -0
  94. package/dist/lib/evg/PLAN_EVG_RENDERER.md +1674 -0
  95. package/dist/lib/evg/PLAN_INSPECTOR.md +788 -0
  96. package/dist/lib/evg/PLAN_LINKS_AND_FORMS.md +157 -0
  97. package/dist/lib/evg/PLAN_NATIVE_HOSTS.md +671 -0
  98. package/dist/lib/evg/PLAN_VECTOR_IR.md +664 -0
  99. package/dist/lib/evg/PLAN_VIEW_TRANSFORM.md +322 -0
  100. package/dist/lib/evg/PathBuilder.rgr +333 -0
  101. package/dist/lib/evg/README.md +1236 -0
  102. package/dist/lib/evg/SPEC.md +1252 -0
  103. package/dist/lib/evg/SVGPathParser.rgr +1199 -0
  104. package/dist/lib/evg/SvgParser.rgr +1736 -0
  105. package/dist/lib/evg/TODO_EVG_RENDERER.md +595 -0
  106. package/dist/lib/evg/VectorShapes.rgr +331 -0
  107. package/dist/lib/evg/VectorStroke.rgr +107 -0
  108. package/dist/lib/evg/VectorViewBox.rgr +380 -0
  109. package/dist/lib/evg/agent/README.md +290 -0
  110. package/dist/lib/evg/agent/evg_agent.rgr +899 -0
  111. package/dist/lib/evg/agent/fixtures/broken.evg.json +7 -0
  112. package/dist/lib/evg/agent/fixtures/card.evg.json +9 -0
  113. package/dist/lib/evg/agent/fixtures/connector.css +63 -0
  114. package/dist/lib/evg/agent/fixtures/connector.evg.json +14 -0
  115. package/dist/lib/evg/agent/fixtures/drawn.evg.json +129 -0
  116. package/dist/lib/evg/agent/fixtures/gradient.evg.json +4 -0
  117. package/dist/lib/evg/agent/fixtures/popover.css +99 -0
  118. package/dist/lib/evg/agent/fixtures/popover.evg.json +30 -0
  119. package/dist/lib/evg/agent/roundtrip.sh +74 -0
  120. package/dist/lib/evg/agent/smoke.sh +230 -0
  121. package/dist/lib/evg/android/README.md +97 -0
  122. package/dist/lib/evg/android/androidstubs/AndroidStubs.kt +175 -0
  123. package/dist/lib/evg/android/androidstubs/Annotation.kt +10 -0
  124. package/dist/lib/evg/android/androidstubs/App.kt +30 -0
  125. package/dist/lib/evg/android/androidstubs/Content.kt +41 -0
  126. package/dist/lib/evg/android/androidstubs/ContentRes.kt +12 -0
  127. package/dist/lib/evg/android/androidstubs/InputMethod.kt +46 -0
  128. package/dist/lib/evg/android/androidstubs/Net.kt +6 -0
  129. package/dist/lib/evg/android/androidstubs/Os.kt +30 -0
  130. package/dist/lib/evg/android/androidstubs/Util.kt +15 -0
  131. package/dist/lib/evg/android/androidstubs/View.kt +113 -0
  132. package/dist/lib/evg/android/androidstubs/Widget.kt +16 -0
  133. package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidEvgSurface.kt +323 -0
  134. package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidTextMeasurer.kt +56 -0
  135. package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/RippleEffect.kt +262 -0
  136. package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtEvgSurface.kt +238 -0
  137. package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtTextMeasurer.kt +85 -0
  138. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgEngineThread.kt +115 -0
  139. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgPainter.kt +231 -0
  140. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgSurface.kt +117 -0
  141. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/RecordingSurface.kt +94 -0
  142. package/dist/lib/evg/apple/README.md +128 -0
  143. package/dist/lib/evg/apple/Sources/CoreGraphicsEvgSurface.swift +329 -0
  144. package/dist/lib/evg/apple/Sources/CoreTextMeasurer.swift +70 -0
  145. package/dist/lib/evg/apple/Sources/EvgEngineQueue.swift +105 -0
  146. package/dist/lib/evg/apple/Sources/EvgPainter.swift +225 -0
  147. package/dist/lib/evg/apple/Sources/EvgSurface.swift +163 -0
  148. package/dist/lib/evg/apple/Sources/RecordingSurface.swift +110 -0
  149. package/dist/lib/evg/bench/EvgLayoutBench.rgr +34 -0
  150. package/dist/lib/evg/bench/README.md +64 -0
  151. package/dist/lib/evg/bench/layout-bench.mjs +316 -0
  152. package/dist/lib/evg/bench/layout-cases.mjs +510 -0
  153. package/dist/lib/evg/bench/layout-conformance.mjs +254 -0
  154. package/dist/lib/evg/bin/.gitignore +17 -0
  155. package/dist/lib/evg/evg_test.rgr +313 -0
  156. package/dist/lib/evg/gl/README.md +198 -0
  157. package/dist/lib/evg/gl/a11y-paint-check.mjs +73 -0
  158. package/dist/lib/evg/gl/blur-check.mjs +399 -0
  159. package/dist/lib/evg/gl/boxmodel.json +1 -0
  160. package/dist/lib/evg/gl/demo.html +46 -0
  161. package/dist/lib/evg/gl/effect-presets.css +285 -0
  162. package/dist/lib/evg/gl/effect-presets.js +63 -0
  163. package/dist/lib/evg/gl/effect-shots.mjs +135 -0
  164. package/dist/lib/evg/gl/evg-a11y.js +566 -0
  165. package/dist/lib/evg/gl/evg-binary.js +160 -0
  166. package/dist/lib/evg/gl/evg-engine.js +296 -0
  167. package/dist/lib/evg/gl/evg-fx.js +237 -0
  168. package/dist/lib/evg/gl/evg-gestures.js +209 -0
  169. package/dist/lib/evg/gl/evg-list.js +167 -0
  170. package/dist/lib/evg/gl/evg-measure.js +186 -0
  171. package/dist/lib/evg/gl/evg-textinput.js +303 -0
  172. package/dist/lib/evg/gl/evg-view.js +144 -0
  173. package/dist/lib/evg/gl/evg-webgl.js +3942 -0
  174. package/dist/lib/evg/gl/fx-check.mjs +998 -0
  175. package/dist/lib/evg/gl/fx-demo.html +166 -0
  176. package/dist/lib/evg/gl/fx-demo.js +2 -0
  177. package/dist/lib/evg/gl/fx-serve.mjs +47 -0
  178. package/dist/lib/evg/gl/gestures-check.mjs +189 -0
  179. package/dist/lib/evg/gl/list-binary-check.mjs +152 -0
  180. package/dist/lib/evg/gl/measure-check.mjs +135 -0
  181. package/dist/lib/evg/gl/rotation-check.mjs +207 -0
  182. package/dist/lib/evg/gl/shift-check.mjs +113 -0
  183. package/dist/lib/evg/gl/stroke-check.mjs +168 -0
  184. package/dist/lib/evg/gl/text-snap-check.mjs +139 -0
  185. package/dist/lib/evg/gl/view-check.mjs +343 -0
  186. package/dist/lib/evg/gl/view-policy-check.mjs +184 -0
  187. package/dist/lib/evg/html/evg-dom.js +318 -0
  188. package/dist/lib/evg/html/evg-html.js +600 -0
  189. package/dist/lib/evg/inspect/README.md +415 -0
  190. package/dist/lib/evg/inspect/browser-smoke.mjs +115 -0
  191. package/dist/lib/evg/inspect/evg-inspect.js +947 -0
  192. package/dist/lib/evg/inspect/shots/css.png +0 -0
  193. package/dist/lib/evg/inspect/shots/dashboard.png +0 -0
  194. package/dist/lib/evg/inspect/shots/pptx-slide.png +0 -0
  195. package/dist/lib/evg/inspect/shots/state.png +0 -0
  196. package/dist/lib/evg/inspect/shots.mjs +226 -0
  197. package/dist/lib/evg/oracle/css-blur.json +576 -0
  198. package/dist/lib/evg/oracle/css-box.json +157 -0
  199. package/dist/lib/evg/oracle/css-timing.json +709 -0
  200. package/dist/lib/evg/oracle/css_blur_oracle.mjs +529 -0
  201. package/dist/lib/evg/oracle/css_box_oracle.mjs +106 -0
  202. package/dist/lib/evg/oracle/css_timing_oracle.mjs +389 -0
  203. package/dist/lib/evg/original/EVGColor.clj +310 -0
  204. package/dist/lib/evg/original/EVGColorContext.rgr +178 -0
  205. package/dist/lib/evg/original/SVGPath.rgr +627 -0
  206. package/dist/lib/evg/original/Vec2.crgr +103 -0
  207. package/dist/lib/evg/ranger.json +9 -0
  208. package/dist/lib/evg/showcase/README.md +246 -0
  209. package/dist/lib/evg/showcase/assets/emblem.svg +30 -0
  210. package/dist/lib/evg/showcase/assets/rosette.svg +22 -0
  211. package/dist/lib/evg/showcase/build.mjs +651 -0
  212. package/dist/lib/evg/showcase/pages/album.tsx +30 -0
  213. package/dist/lib/evg/showcase/pages/boxmodel.tsx +37 -0
  214. package/dist/lib/evg/showcase/pages/cards.tsx +41 -0
  215. package/dist/lib/evg/showcase/pages/chart_api.tsx +170 -0
  216. package/dist/lib/evg/showcase/pages/charts.tsx +219 -0
  217. package/dist/lib/evg/showcase/pages/drawing.tsx +160 -0
  218. package/dist/lib/evg/showcase/pages/emoji.tsx +91 -0
  219. package/dist/lib/evg/showcase/pages/flex.tsx +46 -0
  220. package/dist/lib/evg/showcase/pages/more.tsx +332 -0
  221. package/dist/lib/evg/showcase/pages/plots.tsx +262 -0
  222. package/dist/lib/evg/showcase/pages/svg.tsx +63 -0
  223. package/dist/lib/evg/showcase/pages/tables.tsx +209 -0
  224. package/dist/lib/evg/showcase/pages/typography.tsx +43 -0
  225. package/dist/lib/evg/showcase/pages/units.tsx +35 -0
  226. package/dist/lib/evg/showcase/pages/variants.tsx +233 -0
  227. package/dist/lib/evg/showcase/pages/vector.tsx +75 -0
  228. package/dist/lib/evg/showcase/pages/views.tsx +223 -0
  229. package/dist/lib/evg/showcase/tests/chart_api_smoke.mjs +339 -0
  230. package/dist/lib/evg/showcase/tests/gl_smoke.mjs +125 -0
  231. package/dist/lib/evg/showcase/themes/chart_api-default.css +15 -0
  232. package/dist/lib/evg/showcase/themes/charts-default.css +29 -0
  233. package/dist/lib/evg/showcase/themes/drawing-default.css +35 -0
  234. package/dist/lib/evg/showcase/themes/more-default.css +128 -0
  235. package/dist/lib/evg/showcase/themes/plots-default.css +18 -0
  236. package/dist/lib/evg/showcase/themes/showcase.css +508 -0
  237. package/dist/lib/evg/showcase/themes/tables-default.css +123 -0
  238. package/dist/lib/evg/showcase/themes/variants-default.css +15 -0
  239. package/dist/lib/evg/showcase/themes/views-default.css +15 -0
  240. package/dist/lib/evg/tools/bench_vs_potrace.mjs +290 -0
  241. package/dist/lib/evg/tools/evg_image_tool.rgr +488 -0
  242. package/dist/lib/evg/tools/evg_trace_bench.rgr +114 -0
  243. package/dist/lib/evg/tools/evg_trace_cli.rgr +662 -0
  244. package/dist/lib/evg/tools/evg_trace_cpp_bench.rgr +83 -0
  245. package/dist/lib/evg/tools/run_trace_bench.sh +74 -0
  246. package/dist/lib/evg/tools/run_trace_cli_smoke.sh +128 -0
  247. package/dist/lib/evg/web/responsive/EvgResponsiveCheck.rgr +271 -0
  248. package/dist/lib/evg/web/responsive/EvgResponsiveDemo.rgr +531 -0
  249. package/dist/lib/evg/web/responsive/README.md +97 -0
  250. package/dist/lib/evg/web/responsive/build.mjs +90 -0
  251. package/dist/lib/evg/web/responsive/dom-check.mjs +169 -0
  252. package/dist/lib/evg/web/responsive/index.html +177 -0
  253. package/dist/lib/evg/web/responsive/smoke.mjs +221 -0
  254. package/dist/lib/evg/web/tools/assets-client.mjs +95 -0
  255. package/dist/lib/evg/web/tools/boot-bench.mjs +189 -0
  256. package/dist/lib/evg/web/tools/inline-assets.mjs +119 -0
  257. package/dist/lib/evg/web/tools/minify.mjs +60 -0
  258. package/dist/lib/evg/web/tracer/build.mjs +87 -0
  259. package/dist/lib/evg/web/tracer/index.html +2805 -0
  260. package/dist/lib/evg/web/tracer/sample.png +0 -0
  261. package/dist/lib/evg/web/tracer/smoke.mjs +1162 -0
  262. package/dist/lib/evgr/Cargo.lock +21 -0
  263. package/dist/lib/evgr/Cargo.toml +19 -0
  264. package/dist/lib/evgr/README.md +140 -0
  265. package/dist/lib/evgr/bench/NativeBench.rgr +246 -0
  266. package/dist/lib/evgr/bench/compare.mjs +252 -0
  267. package/dist/lib/evgr/bench/speed.mjs +277 -0
  268. package/dist/lib/evgr/bin/.gitignore +3 -0
  269. package/dist/lib/evgr/src/bin/bench.rs +106 -0
  270. package/dist/lib/evgr/src/bin/smoke.rs +13 -0
  271. package/dist/lib/evgr/src/grid.rs +730 -0
  272. package/dist/lib/evgr/src/lib.rs +1003 -0
  273. package/dist/lib/evgr/src/style.rs +468 -0
  274. package/dist/lib/evgr/src/text.rs +83 -0
  275. package/dist/lib/image/BitReader.rgr +171 -0
  276. package/dist/lib/image/Buffer.rgr +173 -0
  277. package/dist/lib/image/DCT.rgr +283 -0
  278. package/dist/lib/image/Deflate.rgr +339 -0
  279. package/dist/lib/image/HuffmanDecoder.rgr +181 -0
  280. package/dist/lib/image/ImageBuffer.rgr +706 -0
  281. package/dist/lib/image/JPEGDecoder.rgr +808 -0
  282. package/dist/lib/image/PNGDecoder.rgr +544 -0
  283. package/dist/lib/image/PNGEncoder.rgr +388 -0
  284. package/dist/lib/image/PPMImage.rgr +224 -0
  285. package/dist/lib/image/ProgressiveJPEGDecoder.rgr +1154 -0
  286. package/dist/lib/image/README.md +33 -0
  287. package/dist/lib/image/RasterBuffer.rgr +294 -0
  288. package/dist/lib/image/VP8BoolDecoder.rgr +163 -0
  289. package/dist/lib/image/WebPDecoder.rgr +433 -0
  290. package/dist/lib/image/WebPLossless.rgr +1035 -0
  291. package/dist/lib/image/WebPLossy.rgr +1929 -0
  292. package/dist/lib/image/ranger.json +9 -0
  293. package/dist/lib/image/testdata/webp/anim_2f_24x16.webp +0 -0
  294. package/dist/lib/image/testdata/webp/fixtures.txt +33 -0
  295. package/dist/lib/image/testdata/webp/gen_fixtures.py +261 -0
  296. package/dist/lib/image/testdata/webp/ll_1x1.webp +0 -0
  297. package/dist/lib/image/testdata/webp/ll_alpha_29x21.webp +0 -0
  298. package/dist/lib/image/testdata/webp/ll_gradient_64x64.webp +0 -0
  299. package/dist/lib/image/testdata/webp/ll_meta_32x32.webp +0 -0
  300. package/dist/lib/image/testdata/webp/ll_noise_17x9.webp +0 -0
  301. package/dist/lib/image/testdata/webp/ll_pal11_23x11.webp +0 -0
  302. package/dist/lib/image/testdata/webp/ll_pal2_19x7.webp +0 -0
  303. package/dist/lib/image/testdata/webp/ll_pal40_16x16.webp +0 -0
  304. package/dist/lib/image/testdata/webp/ll_pal4_13x5.webp +0 -0
  305. package/dist/lib/image/testdata/webp/ll_photo_40x30.webp +0 -0
  306. package/dist/lib/image/testdata/webp/ll_predictors_64x32.webp +0 -0
  307. package/dist/lib/image/testdata/webp/ly_1x1.webp +0 -0
  308. package/dist/lib/image/testdata/webp/ly_alph_raw_f0_21x13.webp +0 -0
  309. package/dist/lib/image/testdata/webp/ly_alph_raw_f1_21x13.webp +0 -0
  310. package/dist/lib/image/testdata/webp/ly_alph_raw_f2_21x13.webp +0 -0
  311. package/dist/lib/image/testdata/webp/ly_alph_raw_f3_21x13.webp +0 -0
  312. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f0_21x13.webp +0 -0
  313. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f1_21x13.webp +0 -0
  314. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f2_21x13.webp +0 -0
  315. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f3_21x13.webp +0 -0
  316. package/dist/lib/image/testdata/webp/ly_alpha_33x17.webp +0 -0
  317. package/dist/lib/image/testdata/webp/ly_exif_15x11.webp +0 -0
  318. package/dist/lib/image/testdata/webp/ly_i16_96x80.webp +0 -0
  319. package/dist/lib/image/testdata/webp/ly_nofilter_24x20.webp +0 -0
  320. package/dist/lib/image/testdata/webp/ly_odd_17x9.webp +0 -0
  321. package/dist/lib/image/testdata/webp/ly_photo_48x40.webp +0 -0
  322. package/dist/lib/image/testdata/webp/ly_q100_20x18.webp +0 -0
  323. package/dist/lib/image/testdata/webp/ly_sharp_40x36.webp +0 -0
  324. package/dist/lib/image/testdata/webp/ly_simple_filter_33x31.webp +0 -0
  325. package/dist/lib/image/testdata/webp/ly_vpx_parts8_37x45.webp +0 -0
  326. package/dist/lib/image/testdata/webp/ly_vpx_skip_96x80.webp +0 -0
  327. package/dist/lib/image/tests/WebPDecodeTool.rgr +81 -0
  328. package/dist/lib/image/tests/WebPDecoderTest.rgr +1162 -0
  329. package/dist/lib/image/tests/run_webp_tests.sh +39 -0
  330. package/dist/lib/rust/RsJson.rgr +468 -0
  331. package/dist/lib/rust/RsPrelude.rgr +1663 -0
  332. package/dist/lib/shell_test.rgr +7 -7
  333. package/dist/lib/stdlib.rgr +21 -5
  334. package/dist/lib/zip/ranger.json +6 -0
  335. package/dist/rgrc.js +87318 -67610
  336. package/package.json +1010 -1040
  337. package/dist/README.md +0 -117
  338. package/dist/package.json +0 -47
@@ -0,0 +1,566 @@
1
+ /**
2
+ * evg-a11y.js — the accessibility tree, as DOM, over the canvas.
3
+ *
4
+ * A screen reader does not look at pixels. It asks the browser for the tree it
5
+ * built out of the DOM, and a <canvas> contributes one empty graphic to that
6
+ * tree no matter what was drawn into it. So the app publishes what the frame
7
+ * MEANS — `EVGA11yTree`, built in Ranger beside the display list — and this
8
+ * file turns it into real DOM elements positioned over the canvas, with the
9
+ * roles, names and states the tree carries.
10
+ *
11
+ * GridApp ─┬─ sceneJson() ─► evg-webgl.js ─► pixels
12
+ * └─ a11yJson() ─► THIS FILE ─► DOM ─► NVDA / VoiceOver / Orca
13
+ *
14
+ * Google Sheets does the same thing for the same reason. There is no other
15
+ * approach that works: `role="application"` plus an announcement region gives a
16
+ * reader nothing to explore, and nothing to put a braille cursor on.
17
+ *
18
+ * What the elements look like
19
+ * ---------------------------
20
+ * They are transparent rather than hidden. `display:none` and
21
+ * `visibility:hidden` remove a node from the accessibility tree, which is the
22
+ * one thing being built here, and `opacity:0` is treated as hidden by some
23
+ * combinations. Transparent ink over a canvas that already drew the same text
24
+ * is visible to the accessibility API and invisible to the eye.
25
+ *
26
+ * They are positioned at their real rectangles, because a rectangle is not
27
+ * decoration: touch exploration on a tablet, a screen magnifier following the
28
+ * cursor, and "route the mouse to the focus" all use it.
29
+ *
30
+ * The container takes no pointer events, so the mouse keeps going to the canvas
31
+ * and the app's own hit testing stays the only hit testing. A reader ACTIVATING
32
+ * an element still arrives as a click on that element — and this file answers it
33
+ * by pressing the app where the element is. That is deliberately not a mapping
34
+ * table from node ids to commands: there is nothing to keep in step with the
35
+ * app, and a button that moved is still pressed in the right place.
36
+ *
37
+ * Focus
38
+ * -----
39
+ * The app owns it. Whatever `tree.focus` names is the only element with
40
+ * `tabindex="0"` — the roving tabindex an ARIA grid is built on — and this file
41
+ * calls `.focus()` on it when it changes. It never tells the app that focus
42
+ * moved, because a mirror that reports focus back gets into a loop with the app
43
+ * that is trying to set it.
44
+ *
45
+ * Updates
46
+ * -------
47
+ * Elements are kept in a map by node id and reused. That is what makes the ids
48
+ * matter: rebuilding the subtree under a reader's cursor loses the cursor, and
49
+ * the ids the tree carries are derived from identity — `sheet/cell:B7` — so a
50
+ * repaint, a scroll and a recalculation all leave the same element in place.
51
+ * A tree whose `gen` has not changed is skipped entirely.
52
+ */
53
+
54
+ const HIDDEN_ROLES = new Set(["none", "presentation"]);
55
+
56
+ // Roles whose own text IS the content, so the element carries it as text and
57
+ // takes no aria-label — a label would override the content and read instead of
58
+ // it. The rest get an aria-label and are containers for their children.
59
+ const CONTENT_ROLES = new Set([
60
+ "text", "heading", "button", "gridcell", "columnheader", "rowheader",
61
+ "checkbox", "radio", "tab", "menuitem", "menuitemcheckbox", "menuitemradio",
62
+ "listitem", "link",
63
+ ]);
64
+
65
+ // A native <button> is better understood by every screen reader than a div with
66
+ // role="button", and it is activatable without any extra key handling. A
67
+ // textbox is a native <input> for the same reason, and for one more: it is
68
+ // the element the text-input bridge edits in. A div with role="textbox" and
69
+ // the value as its text is a textbox a reader can read and not type into —
70
+ // measured with the RealTrainer port: the reader found the field, Tab could
71
+ // not leave it for the send button, and the mouse could not select in it.
72
+ function elementFor(role) {
73
+ if (role === "button") return document.createElement("button");
74
+ if (role === "textbox") {
75
+ const input = document.createElement("input");
76
+ input.type = "text";
77
+ input.autocapitalize = "off";
78
+ input.autocomplete = "off";
79
+ input.spellcheck = false;
80
+ return input;
81
+ }
82
+ return document.createElement("div");
83
+ }
84
+
85
+ /**
86
+ * NOTHING HERE MAY PAINT. The canvas underneath already drew all of it, so a
87
+ * mirror node that puts one pixel on the screen is a second copy of the page
88
+ * — and, because the mirror is rebuilt on its own schedule rather than every
89
+ * frame, a second copy AT AN OLDER SCROLL OFFSET. Two pages superimposed.
90
+ *
91
+ * `color: transparent` is not enough to promise that, and the gap is exactly
92
+ * where it hurts: the mirror makes NATIVE controls, because a real <button>
93
+ * and a real <input> are what readers understand and what the text bridge
94
+ * edits in — and a native control on iOS is painted through
95
+ * `-webkit-text-fill-color` and `-webkit-appearance`, neither of which
96
+ * `color` reaches. Everything a UA can decide to draw is turned off here by
97
+ * name, and `evg:a11y:paint` holds the list.
98
+ */
99
+ export function styleBase(el) {
100
+ const s = el.style;
101
+ s.position = "absolute";
102
+ s.margin = "0";
103
+ s.padding = "0";
104
+ s.border = "0";
105
+ s.background = "transparent";
106
+ s.color = "transparent";
107
+ s.font = "inherit";
108
+ // SIXTEEN PIXELS, AND IT IS NOT A DESIGN CHOICE. Nothing here paints, so
109
+ // the size is invisible — but iOS Safari zooms the whole page in when a
110
+ // text field it is focusing computes to less than 16px, and the mirror's
111
+ // `<input>` is what a tap on a drawn field focuses. The page then sat at
112
+ // some magnification the person had to pinch their way out of, over a
113
+ // canvas that used to refuse the pinch as well.
114
+ //
115
+ // It costs nothing: the node is clipped by `overflow: hidden` below and its
116
+ // text is transparent either way. Anyone tidying this back to 10px would
117
+ // bring the zoom back with it, which is why `evg:a11y:paint` holds it.
118
+ s.fontSize = "16px";
119
+ s.lineHeight = "1";
120
+ s.overflow = "hidden";
121
+ s.whiteSpace = "pre";
122
+ s.outline = "none";
123
+ // The three a native control paints itself with, which `color` does not
124
+ // reach: the fill iOS uses for button and input text, the platform chrome
125
+ // it would draw around them, and the caret that blinks in a text field
126
+ // even when its text is invisible.
127
+ s.webkitTextFillColor = "transparent";
128
+ s.webkitAppearance = "none";
129
+ s.appearance = "none";
130
+ s.caretColor = "transparent";
131
+ // And the two that paint outside the box, so they are not clipped away by
132
+ // `overflow: hidden` above.
133
+ s.textShadow = "none";
134
+ s.boxShadow = "none";
135
+ // The canvas underneath already drew all of this. Nothing here should paint,
136
+ // and nothing here should take a click away from it.
137
+ s.pointerEvents = "none";
138
+ s.userSelect = "none";
139
+ }
140
+
141
+ function setAttr(el, name, value) {
142
+ if (value === null || value === undefined || value === "") {
143
+ if (el.hasAttribute(name)) el.removeAttribute(name);
144
+ } else if (el.getAttribute(name) !== String(value)) {
145
+ el.setAttribute(name, String(value));
146
+ }
147
+ }
148
+
149
+ const TRI = { 1: "false", 2: "true", 3: "mixed" };
150
+
151
+ /**
152
+ * Create a mirror over a canvas.
153
+ *
154
+ * host element that positions the canvas; the mirror is appended to it
155
+ * and must sit exactly over the canvas, so the host needs
156
+ * `position: relative` and no padding of its own.
157
+ * canvas hidden from the accessibility tree, since the mirror replaces it
158
+ * onActivate(node) a reader pressed something. Default: press the app at the
159
+ * node's centre.
160
+ * scale app pixels → CSS pixels, when the canvas is not drawn 1:1
161
+ */
162
+ /**
163
+ * @param {object} opts
164
+ * @param {"roving"|"all"} [opts.tabbable] "roving" (the default, and what an
165
+ * ARIA grid wants): exactly one element is tabbable, the one the app
166
+ * says has focus. "all": every focusable node is a tab stop in tree
167
+ * order — a page of buttons and fields, where Tab has to walk from the
168
+ * field to the send button beside it.
169
+ * @param {(node: object) => void} [opts.onFocus] Focus moved inside the
170
+ * mirror by the reader or the keyboard — not by the app. Reported so
171
+ * the app can follow; the mirror never reports the focus it set itself.
172
+ */
173
+ export function createA11yMirror(host, { canvas, onActivate, onFocus, canMoveFocus, scale = 1, label = "Application", tabbable = "roving" } = {}) {
174
+ const root = document.createElement("div");
175
+ root.className = "evg-a11y";
176
+ root.style.position = "absolute";
177
+ root.style.left = "0";
178
+ root.style.top = "0";
179
+ root.style.right = "0";
180
+ root.style.bottom = "0";
181
+ root.style.overflow = "hidden";
182
+ root.style.pointerEvents = "none";
183
+ host.appendChild(root);
184
+
185
+ if (canvas) {
186
+ // The canvas is scenery now. Leaving it in the accessibility tree gives the
187
+ // reader an empty graphic beside a full tree of real nodes.
188
+ canvas.setAttribute("aria-hidden", "true");
189
+ }
190
+
191
+ const els = new Map(); // node id → element
192
+ let lastGen = -1;
193
+ let lastFocus = null;
194
+ let settingFocus = false;
195
+ // The tab stop held open while the app names no focus — see `update`.
196
+ let entryStop = null;
197
+
198
+ function activate(node) {
199
+ if (onActivate) onActivate(node);
200
+ }
201
+
202
+ function ensure(node) {
203
+ let entry = els.get(node.id);
204
+ // A node that changed role cannot keep its element: a <button> does not
205
+ // become a gridcell, and a reader would keep the old one's semantics.
206
+ if (entry && entry.role !== node.role) {
207
+ entry.el.remove();
208
+ entry = null;
209
+ }
210
+ if (!entry) {
211
+ const el = elementFor(node.role);
212
+ styleBase(el);
213
+ if (node.role === "button") {
214
+ el.type = "button";
215
+ el.tabIndex = -1;
216
+ }
217
+ if (node.role === "textbox") {
218
+ el.tabIndex = -1;
219
+ // The bridge takes the pointer while it edits here; until then the
220
+ // canvas keeps every hit test, as for every other element.
221
+ el.style.pointerEvents = "none";
222
+ }
223
+ el.dataset.a11yId = node.id;
224
+ el.addEventListener("click", (ev) => {
225
+ ev.preventDefault();
226
+ // The elements are NESTED — a row is inside its menu is inside the
227
+ // bar — so a click that keeps bubbling reaches every ancestor's
228
+ // listener too, and each one presses the app at ITS OWN centre. One
229
+ // activation on a menu row then became three: the row, the menu
230
+ // behind it, and the page behind that.
231
+ ev.stopPropagation();
232
+ const n = els.get(node.id);
233
+ if (n) activate(n.node);
234
+ });
235
+ entry = { el, role: node.role, node };
236
+ els.set(node.id, entry);
237
+ }
238
+ entry.node = node;
239
+ return entry;
240
+ }
241
+
242
+ function applyAria(el, node, byId) {
243
+ const role = node.role;
244
+ // A native <button> already has the role; anything else is a div and needs
245
+ // to be told what it is.
246
+ if (el.tagName !== "BUTTON") setAttr(el, "role", role);
247
+
248
+ const content = CONTENT_ROLES.has(role);
249
+ if (role === "textbox") {
250
+ // A field's label and its contents are two different strings, and a
251
+ // reader announces both: "Formula, equals SUM open paren…". The value
252
+ // is the input's own; while the input has focus the bridge is writing
253
+ // it and the app is mirroring it, so it is left alone.
254
+ setAttr(el, "aria-label", node.name);
255
+ const text = node.value || "";
256
+ if (document.activeElement !== el && el.value !== text) el.value = text;
257
+ } else if (role === "status") {
258
+ const text = node.value || node.name || "";
259
+ if (el.textContent !== text) el.textContent = text;
260
+ setAttr(el, "aria-label", null);
261
+ } else if (content) {
262
+ const text = node.name || "";
263
+ if (el.textContent !== text) el.textContent = text;
264
+ setAttr(el, "aria-label", null);
265
+ } else {
266
+ setAttr(el, "aria-label", node.name);
267
+ }
268
+
269
+ setAttr(el, "aria-description", node.desc);
270
+ // Some controls are a known role doing an unfamiliar job. A sortable row
271
+ // is a `button`, and a reader told only that learns nothing about being
272
+ // able to move it.
273
+ setAttr(el, "aria-roledescription", node.roledesc);
274
+ // Open or closed. A trigger that never says which is a trigger a reader
275
+ // cannot tell has already been pressed.
276
+ setAttr(el, "aria-expanded", node.expanded ? TRI[node.expanded] || null : null);
277
+ // What it opens. The tree has carried `haspopup` ("menu", "listbox",
278
+ // "dialog") and serialised it all along; nothing here read it, so a select
279
+ // trigger or a date picker's button reached a reader as a plain button
280
+ // that happened to expand.
281
+ setAttr(el, "aria-haspopup", node.haspopup || null);
282
+ setAttr(el, "aria-disabled", node.disabled ? "true" : null);
283
+ // A native button that is disabled is not a tab stop and not activatable,
284
+ // which is what the drawn one is.
285
+ if (el.tagName === "BUTTON") el.disabled = !!node.disabled;
286
+ if (tabbable === "all" && !(el.tagName === "BUTTON" && node.disabled)) {
287
+ const stop = node.focusable ? 0 : -1;
288
+ if (el.tabIndex !== stop) el.tabIndex = stop;
289
+ }
290
+ // Three states, passed STRAIGHT THROUGH: "true", "false", or absent.
291
+ // `node.readonly ? "true" : null` was right while the field was a boolean
292
+ // and became a bug the moment it became a string — "false" is truthy in
293
+ // JavaScript, so a field that had been asked and answered no would have
294
+ // been mirrored as yes. Absent and "false" are different claims, which is
295
+ // the entire reason these are strings.
296
+ setAttr(el, "aria-readonly", node.readonly || null);
297
+ setAttr(el, "aria-required", node.required || null);
298
+ setAttr(el, "aria-invalid", node.invalid || null);
299
+ setAttr(el, "aria-modal", node.modal ? "true" : null);
300
+ // A tab says "false" out loud when it is not the selected one, as Radix's
301
+ // (and the APG's) do; elsewhere an unselected node carries nothing.
302
+ setAttr(el, "aria-selected", node.selected ? "true" : node.role === "tab" ? "false" : null);
303
+ // A toggle button that says so in its own field. `node.pressed` has been
304
+ // in the tree and in the serialisation all along and NOTHING read it here:
305
+ // the only route to `aria-pressed` was through `checked`, so a control
306
+ // that set `pressed` honestly got nothing. Checked first, because a
307
+ // checkbox mirrored as a toggle would be worse than either.
308
+ if (!node.checked && node.pressed) {
309
+ setAttr(el, "aria-pressed", role === "button" ? TRI[node.pressed] || null : null);
310
+ setAttr(el, "aria-checked", null);
311
+ } else if (node.checked) {
312
+ const state = TRI[node.checked] || null;
313
+ if (role === "button") {
314
+ setAttr(el, "aria-pressed", state);
315
+ setAttr(el, "aria-checked", null);
316
+ } else {
317
+ setAttr(el, "aria-checked", state);
318
+ setAttr(el, "aria-pressed", null);
319
+ }
320
+ } else {
321
+ setAttr(el, "aria-pressed", null);
322
+ setAttr(el, "aria-checked", null);
323
+ }
324
+ // A number the reader announces. `now` is deliberately absent on an
325
+ // indeterminate progress bar: a range with no position in it is how every
326
+ // platform spells "busy, length unknown", and writing 0 there instead
327
+ // would have the reader say the work has not started.
328
+ setAttr(el, "aria-valuenow", node.now == null ? null : node.now);
329
+ setAttr(el, "aria-valuemin", node.min == null ? null : node.min);
330
+ setAttr(el, "aria-valuemax", node.max == null ? null : node.max);
331
+ // Virtualization: the counts are the whole sheet, the indices say where the
332
+ // emitted rows sit in it.
333
+ setAttr(el, "aria-rowcount", node.rows || null);
334
+ setAttr(el, "aria-colcount", node.cols || null);
335
+ setAttr(el, "aria-rowindex", node.row || null);
336
+ setAttr(el, "aria-colindex", node.col || null);
337
+ setAttr(el, "aria-posinset", node.pos || null);
338
+ setAttr(el, "aria-setsize", node.size || null);
339
+ // A flat tree carries its nesting HERE and nowhere else: without it a
340
+ // reader announces a tree as a list of siblings all at the same depth.
341
+ setAttr(el, "aria-level", node.level || null);
342
+ // What a key MEANS on a splitter, and the only thing that tells a
343
+ // horizontal one from a vertical one — every other observation about the
344
+ // two is identical.
345
+ setAttr(el, "aria-orientation", node.orientation || null);
346
+ setAttr(el, "aria-current", node.current || null);
347
+ setAttr(el, "aria-live", node.live === 2 ? "assertive" : node.live === 1 ? "polite" : null);
348
+
349
+ const b = node.b || [0, 0, 0, 0];
350
+ const parent = hostNode(node, byId);
351
+ const pb = parent ? parent.b || [0, 0, 0, 0] : [0, 0, 0, 0];
352
+ // Relative to the element this one is placed IN — see `hostNode` — because
353
+ // an absolutely positioned ancestor is the containing block for everything
354
+ // under it.
355
+ const left = (b[0] - pb[0]) * scale;
356
+ const top = (b[1] - pb[1]) * scale;
357
+ const w = Math.max(0, b[2] * scale);
358
+ const h = Math.max(0, b[3] * scale);
359
+ const s = el.style;
360
+ if (s.left !== left + "px") s.left = left + "px";
361
+ if (s.top !== top + "px") s.top = top + "px";
362
+ if (s.width !== w + "px") s.width = w + "px";
363
+ if (s.height !== h + "px") s.height = h + "px";
364
+ }
365
+
366
+ /** Hide everything a modal dialog is covering. A reader that can still walk
367
+ * the sheet behind an open dialog will walk it, and none of those keys are
368
+ * going anywhere. */
369
+ function applyModal(tree, byId) {
370
+ const modal = tree.nodes.find((n) => n.modal);
371
+ const keep = new Set();
372
+ if (modal) {
373
+ let at = modal;
374
+ while (at) {
375
+ keep.add(at.id);
376
+ at = at.p ? byId.get(at.p) : null;
377
+ }
378
+ }
379
+ // Only the top-level regions are hidden or shown: hiding deeper would have
380
+ // to be undone node by node, and hiding a subtree the dialog is inside of
381
+ // would hide the dialog.
382
+ for (const n of tree.nodes) {
383
+ if (n.p !== tree.root) continue;
384
+ const entry = els.get(n.id);
385
+ if (!entry) continue;
386
+ const hide = modal && !keep.has(n.id);
387
+ setAttr(entry.el, "aria-hidden", hide ? "true" : null);
388
+ }
389
+ }
390
+
391
+ // The node whose element holds this one's. Usually the parent; but a
392
+ // textbox is a native <input>, which can hold no children, so a node under
393
+ // one — a password field's show/hide button, drawn inside the field's box —
394
+ // goes under the nearest ancestor that can hold it, and is placed relative
395
+ // to that one's box. Appended into the <input> it had no box at all: the
396
+ // reader was told of a button that was nowhere, and a pointer aimed at its
397
+ // rectangle landed at the page's corner.
398
+ function hostNode(node, byId) {
399
+ let at = node.p ? byId.get(node.p) : null;
400
+ while (at && at.role === "textbox") at = at.p ? byId.get(at.p) : null;
401
+ return at;
402
+ }
403
+
404
+ function update(tree) {
405
+ if (!tree || !tree.nodes) return { skipped: true };
406
+ if (tree.gen === lastGen && tree.focus === lastFocus) return { skipped: true };
407
+ lastGen = tree.gen;
408
+
409
+ const byId = new Map();
410
+ for (const n of tree.nodes) byId.set(n.id, n);
411
+
412
+ const seen = new Set();
413
+ for (const node of tree.nodes) {
414
+ if (HIDDEN_ROLES.has(node.role)) continue;
415
+ seen.add(node.id);
416
+ const entry = ensure(node);
417
+ applyAria(entry.el, node, byId);
418
+ // Re-parent only when it actually changed: moving an element the reader
419
+ // is sitting on drops its cursor.
420
+ const host = hostNode(node, byId);
421
+ const parentEl = host ? (els.get(host.id) || {}).el : root;
422
+ const want = parentEl || root;
423
+ if (entry.el.parentNode !== want) want.appendChild(entry.el);
424
+ }
425
+
426
+ for (const [id, entry] of els) {
427
+ if (!seen.has(id)) {
428
+ entry.el.remove();
429
+ els.delete(id);
430
+ }
431
+ }
432
+
433
+ // `aria-describedby`, once every element exists: the message a field
434
+ // points at is usually built AFTER the field. The target gets a DOM id
435
+ // derived from its node id; the resolved `aria-description` is dropped
436
+ // when the reference resolves, or a reader would say the sentence twice.
437
+ // A reference to a node that is not in the mirror falls back to the text.
438
+ for (const node of tree.nodes) {
439
+ const entry = els.get(node.id);
440
+ if (!entry) continue;
441
+ const target = node.describedby ? els.get(node.describedby) : null;
442
+ if (target) {
443
+ const domId = "evg-a11y-" + String(node.describedby).replace(/[^A-Za-z0-9_-]/g, "_");
444
+ if (target.el.id !== domId) target.el.id = domId;
445
+ setAttr(entry.el, "aria-describedby", domId);
446
+ setAttr(entry.el, "aria-description", null);
447
+ } else {
448
+ setAttr(entry.el, "aria-describedby", null);
449
+ }
450
+ // `aria-controls` and `aria-labelledby` resolve the same way: a tab
451
+ // names its panel, the panel is named by its tab. A reference to a node
452
+ // that is not in the mirror (an inactive panel) is left off rather than
453
+ // pointing at nothing, which axe reports as a broken reference.
454
+ for (const [key, attr] of [["controls", "aria-controls"], ["labelledby", "aria-labelledby"]]) {
455
+ const ref = node[key];
456
+ const to = ref ? els.get(ref) : null;
457
+ if (to) {
458
+ const domId = "evg-a11y-" + String(ref).replace(/[^A-Za-z0-9_-]/g, "_");
459
+ if (to.el.id !== domId) to.el.id = domId;
460
+ setAttr(entry.el, attr, domId);
461
+ } else {
462
+ setAttr(entry.el, attr, null);
463
+ }
464
+ }
465
+ }
466
+
467
+ applyModal(tree, byId);
468
+
469
+ // Roving tabindex: exactly one element in the whole mirror is tabbable, and
470
+ // it is the one the app says has focus. With every focusable a tab stop
471
+ // the app's focus is still followed, only nothing is taken away.
472
+ if (tabbable !== "all" && tree.focus !== lastFocus) {
473
+ const prev = lastFocus ? els.get(lastFocus) : null;
474
+ if (prev) prev.el.tabIndex = -1;
475
+ }
476
+ const focused = tree.focus ? els.get(tree.focus) : null;
477
+ if (focused) {
478
+ if (tabbable !== "all") focused.el.tabIndex = 0;
479
+ // `canMoveFocus` is how a host that parks the keyboard somewhere else —
480
+ // a text-input bridge editing in its own transparent <input> — says so.
481
+ // The tab stop still moves; only the `.focus()` is withheld, because two
482
+ // elements calling `.focus()` for one field take it off each other.
483
+ if (document.activeElement !== focused.el && (!canMoveFocus || canMoveFocus(focused.node))) {
484
+ settingFocus = true;
485
+ try {
486
+ focused.el.focus({ preventScroll: true });
487
+ } finally {
488
+ settingFocus = false;
489
+ }
490
+ }
491
+ }
492
+ lastFocus = tree.focus;
493
+
494
+ // THE WAY IN. An app that has not been touched yet has nothing focused,
495
+ // and a roving tabindex read literally then makes NO element tabbable —
496
+ // so Tab walks straight past the whole widget and there is no way to reach
497
+ // it from the keyboard at all. That is not the roving pattern; a roving
498
+ // pattern always has one tab stop, and before anything is chosen it is the
499
+ // first focusable node. Once the app names a focus this does nothing.
500
+ if (tabbable !== "all") {
501
+ if (!focused) {
502
+ if (entryStop && (!entryStop.isConnected || entryStop.tabIndex !== 0)) entryStop = null;
503
+ if (!entryStop) {
504
+ for (const node of tree.nodes) {
505
+ if (!node.focusable || node.disabled) continue;
506
+ const entry = els.get(node.id);
507
+ if (!entry) continue;
508
+ entry.el.tabIndex = 0;
509
+ entryStop = entry.el;
510
+ break;
511
+ }
512
+ }
513
+ } else if (entryStop) {
514
+ if (entryStop !== focused.el && entryStop.isConnected) entryStop.tabIndex = -1;
515
+ entryStop = null;
516
+ }
517
+ }
518
+
519
+ return { nodes: tree.nodes.length, elements: els.size, gen: tree.gen };
520
+ }
521
+
522
+ function destroy() {
523
+ root.remove();
524
+ els.clear();
525
+ if (canvas) canvas.removeAttribute("aria-hidden");
526
+ }
527
+
528
+ // Focus that moved on its own — Tab, or a reader's cursor — is reported.
529
+ // The app's own `.focus()` above is not: `settingFocus` is what tells them
530
+ // apart, and a mirror that reported its own focus back would loop with the
531
+ // app that is setting it.
532
+ root.addEventListener("focusin", (ev) => {
533
+ if (settingFocus || !onFocus) return;
534
+ const el = ev.target && ev.target.closest ? ev.target.closest("[data-a11y-id]") : null;
535
+ if (!el) return;
536
+ const entry = els.get(el.dataset.a11yId);
537
+ if (entry) onFocus(entry.node);
538
+ });
539
+
540
+ root.setAttribute("aria-label", label);
541
+ return {
542
+ root,
543
+ update,
544
+ destroy,
545
+ /** The element a node is mirrored as, or null — the bridge edits in it. */
546
+ elementOf(id) {
547
+ const entry = els.get(id);
548
+ return entry ? entry.el : null;
549
+ },
550
+ get size() {
551
+ return els.size;
552
+ },
553
+ isSettingFocus() {
554
+ return settingFocus;
555
+ },
556
+ };
557
+ }
558
+
559
+ /** The default answer to "a reader pressed this": press the app where it is.
560
+ * No mapping table, and nothing to keep in step with the app's own commands. */
561
+ export function pressAtCentre(node, press) {
562
+ const b = node.b || [0, 0, 0, 0];
563
+ const cx = Math.round(b[0] + b[2] / 2);
564
+ const cy = Math.round(b[1] + b[3] / 2);
565
+ press(cx, cy);
566
+ }