@react-x11/components 0.3.0 → 0.5.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 (314) hide show
  1. package/README.md +131 -4
  2. package/dist/charts/components.d.ts +3 -1
  3. package/dist/charts/components.d.ts.map +1 -1
  4. package/dist/charts/components.js +8 -4
  5. package/dist/charts/components.js.map +1 -1
  6. package/dist/charts/node.d.ts +13 -6
  7. package/dist/charts/node.d.ts.map +1 -1
  8. package/dist/charts/node.js +75 -24
  9. package/dist/charts/node.js.map +1 -1
  10. package/dist/charts/render.d.ts +4 -0
  11. package/dist/charts/render.d.ts.map +1 -1
  12. package/dist/charts/render.js +11 -6
  13. package/dist/charts/render.js.map +1 -1
  14. package/dist/code-editor/node.d.ts +47 -16
  15. package/dist/code-editor/node.d.ts.map +1 -1
  16. package/dist/code-editor/node.js +121 -51
  17. package/dist/code-editor/node.js.map +1 -1
  18. package/dist/flow/draw.d.ts +19 -1
  19. package/dist/flow/draw.d.ts.map +1 -1
  20. package/dist/flow/draw.js +64 -35
  21. package/dist/flow/draw.js.map +1 -1
  22. package/dist/flow/index.d.ts.map +1 -1
  23. package/dist/flow/index.js +24 -8
  24. package/dist/flow/index.js.map +1 -1
  25. package/dist/flow/model.d.ts +4 -2
  26. package/dist/flow/model.d.ts.map +1 -1
  27. package/dist/flow/model.js +4 -2
  28. package/dist/flow/model.js.map +1 -1
  29. package/dist/flow/node.d.ts +40 -9
  30. package/dist/flow/node.d.ts.map +1 -1
  31. package/dist/flow/node.js +142 -53
  32. package/dist/flow/node.js.map +1 -1
  33. package/dist/flow/types.d.ts +46 -21
  34. package/dist/flow/types.d.ts.map +1 -1
  35. package/dist/formula/index.d.ts +4 -2
  36. package/dist/formula/index.d.ts.map +1 -1
  37. package/dist/formula/index.js.map +1 -1
  38. package/dist/formula/node.d.ts +5 -1
  39. package/dist/formula/node.d.ts.map +1 -1
  40. package/dist/formula/node.js +19 -3
  41. package/dist/formula/node.js.map +1 -1
  42. package/dist/html/controls.d.ts +4 -1
  43. package/dist/html/controls.d.ts.map +1 -1
  44. package/dist/html/controls.js.map +1 -1
  45. package/dist/html/css/cascade.d.ts +11 -5
  46. package/dist/html/css/cascade.d.ts.map +1 -1
  47. package/dist/html/css/cascade.js +18 -8
  48. package/dist/html/css/cascade.js.map +1 -1
  49. package/dist/html/css/parse.d.ts +15 -10
  50. package/dist/html/css/parse.d.ts.map +1 -1
  51. package/dist/html/css/parse.js +41 -16
  52. package/dist/html/css/parse.js.map +1 -1
  53. package/dist/html/css/style.d.ts +3 -0
  54. package/dist/html/css/style.d.ts.map +1 -1
  55. package/dist/html/css/style.js +3 -1
  56. package/dist/html/css/style.js.map +1 -1
  57. package/dist/html/css/values.d.ts +14 -1
  58. package/dist/html/css/values.d.ts.map +1 -1
  59. package/dist/html/css/values.js +11 -8
  60. package/dist/html/css/values.js.map +1 -1
  61. package/dist/html/index.d.ts.map +1 -1
  62. package/dist/html/index.js +1 -0
  63. package/dist/html/index.js.map +1 -1
  64. package/dist/html/layout/boxes.d.ts +12 -5
  65. package/dist/html/layout/boxes.d.ts.map +1 -1
  66. package/dist/html/layout/boxes.js +7 -4
  67. package/dist/html/layout/boxes.js.map +1 -1
  68. package/dist/html/layout/inline.d.ts.map +1 -1
  69. package/dist/html/layout/inline.js +22 -1
  70. package/dist/html/layout/inline.js.map +1 -1
  71. package/dist/html/node.d.ts +22 -8
  72. package/dist/html/node.d.ts.map +1 -1
  73. package/dist/html/node.js +97 -15
  74. package/dist/html/node.js.map +1 -1
  75. package/dist/html/paint.d.ts +3 -0
  76. package/dist/html/paint.d.ts.map +1 -1
  77. package/dist/html/paint.js +2 -2
  78. package/dist/html/paint.js.map +1 -1
  79. package/dist/index.d.ts +11 -1
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +26 -0
  82. package/dist/index.js.map +1 -1
  83. package/dist/maps/clip.d.ts +43 -0
  84. package/dist/maps/clip.d.ts.map +1 -0
  85. package/dist/maps/clip.js +171 -0
  86. package/dist/maps/clip.js.map +1 -0
  87. package/dist/maps/gzip.d.ts +15 -0
  88. package/dist/maps/gzip.d.ts.map +1 -0
  89. package/dist/maps/gzip.js +56 -0
  90. package/dist/maps/gzip.js.map +1 -0
  91. package/dist/maps/index.d.ts +55 -0
  92. package/dist/maps/index.d.ts.map +1 -0
  93. package/dist/maps/index.js +141 -0
  94. package/dist/maps/index.js.map +1 -0
  95. package/dist/maps/labels.d.ts +123 -0
  96. package/dist/maps/labels.d.ts.map +1 -0
  97. package/dist/maps/labels.js +323 -0
  98. package/dist/maps/labels.js.map +1 -0
  99. package/dist/maps/mvt.d.ts +244 -0
  100. package/dist/maps/mvt.d.ts.map +1 -0
  101. package/dist/maps/mvt.js +820 -0
  102. package/dist/maps/mvt.js.map +1 -0
  103. package/dist/maps/node.d.ts +278 -0
  104. package/dist/maps/node.d.ts.map +1 -0
  105. package/dist/maps/node.js +1459 -0
  106. package/dist/maps/node.js.map +1 -0
  107. package/dist/maps/overlay.d.ts +157 -0
  108. package/dist/maps/overlay.d.ts.map +1 -0
  109. package/dist/maps/overlay.js +472 -0
  110. package/dist/maps/overlay.js.map +1 -0
  111. package/dist/maps/paint.d.ts +226 -0
  112. package/dist/maps/paint.d.ts.map +1 -0
  113. package/dist/maps/paint.js +705 -0
  114. package/dist/maps/paint.js.map +1 -0
  115. package/dist/maps/proj.d.ts +281 -0
  116. package/dist/maps/proj.d.ts.map +1 -0
  117. package/dist/maps/proj.js +451 -0
  118. package/dist/maps/proj.js.map +1 -0
  119. package/dist/maps/sources.d.ts +256 -0
  120. package/dist/maps/sources.d.ts.map +1 -0
  121. package/dist/maps/sources.js +210 -0
  122. package/dist/maps/sources.js.map +1 -0
  123. package/dist/maps/style.d.ts +141 -0
  124. package/dist/maps/style.d.ts.map +1 -0
  125. package/dist/maps/style.js +147 -0
  126. package/dist/maps/style.js.map +1 -0
  127. package/dist/maps/styles.d.ts +82 -0
  128. package/dist/maps/styles.d.ts.map +1 -0
  129. package/dist/maps/styles.js +898 -0
  130. package/dist/maps/styles.js.map +1 -0
  131. package/dist/maps/tiles.d.ts +283 -0
  132. package/dist/maps/tiles.d.ts.map +1 -0
  133. package/dist/maps/tiles.js +469 -0
  134. package/dist/maps/tiles.js.map +1 -0
  135. package/dist/maps/types.d.ts +244 -0
  136. package/dist/maps/types.d.ts.map +1 -0
  137. package/dist/maps/types.js +2 -0
  138. package/dist/maps/types.js.map +1 -0
  139. package/dist/qml/containers.d.ts +3 -0
  140. package/dist/qml/containers.d.ts.map +1 -0
  141. package/dist/qml/containers.js +331 -0
  142. package/dist/qml/containers.js.map +1 -0
  143. package/dist/qml/controls.d.ts +12 -0
  144. package/dist/qml/controls.d.ts.map +1 -0
  145. package/dist/qml/controls.js +21 -0
  146. package/dist/qml/controls.js.map +1 -0
  147. package/dist/qml/globals.d.ts +6 -0
  148. package/dist/qml/globals.d.ts.map +1 -0
  149. package/dist/qml/globals.js +25 -0
  150. package/dist/qml/globals.js.map +1 -0
  151. package/dist/qml/index.d.ts +15 -0
  152. package/dist/qml/index.d.ts.map +1 -0
  153. package/dist/qml/index.js +25 -0
  154. package/dist/qml/index.js.map +1 -0
  155. package/dist/qml/interaction.d.ts +12 -0
  156. package/dist/qml/interaction.d.ts.map +1 -0
  157. package/dist/qml/interaction.js +246 -0
  158. package/dist/qml/interaction.js.map +1 -0
  159. package/dist/qml/ir.d.ts +106 -0
  160. package/dist/qml/ir.d.ts.map +1 -0
  161. package/dist/qml/ir.js +7 -0
  162. package/dist/qml/ir.js.map +1 -0
  163. package/dist/qml/layouts.d.ts +3 -0
  164. package/dist/qml/layouts.d.ts.map +1 -0
  165. package/dist/qml/layouts.js +122 -0
  166. package/dist/qml/layouts.js.map +1 -0
  167. package/dist/qml/models.d.ts +14 -0
  168. package/dist/qml/models.d.ts.map +1 -0
  169. package/dist/qml/models.js +80 -0
  170. package/dist/qml/models.js.map +1 -0
  171. package/dist/qml/objects.d.ts +228 -0
  172. package/dist/qml/objects.d.ts.map +1 -0
  173. package/dist/qml/objects.js +1103 -0
  174. package/dist/qml/objects.js.map +1 -0
  175. package/dist/qml/parse.d.ts +10 -0
  176. package/dist/qml/parse.d.ts.map +1 -0
  177. package/dist/qml/parse.js +607 -0
  178. package/dist/qml/parse.js.map +1 -0
  179. package/dist/qml/qtquick.d.ts +7 -0
  180. package/dist/qml/qtquick.d.ts.map +1 -0
  181. package/dist/qml/qtquick.js +347 -0
  182. package/dist/qml/qtquick.js.map +1 -0
  183. package/dist/qml/react.d.ts +83 -0
  184. package/dist/qml/react.d.ts.map +1 -0
  185. package/dist/qml/react.js +323 -0
  186. package/dist/qml/react.js.map +1 -0
  187. package/dist/qml/resolver.d.ts +38 -0
  188. package/dist/qml/resolver.d.ts.map +1 -0
  189. package/dist/qml/resolver.js +64 -0
  190. package/dist/qml/resolver.js.map +1 -0
  191. package/dist/qml/slots.d.ts +65 -0
  192. package/dist/qml/slots.d.ts.map +1 -0
  193. package/dist/qml/slots.js +230 -0
  194. package/dist/qml/slots.js.map +1 -0
  195. package/dist/qml/states.d.ts +5 -0
  196. package/dist/qml/states.d.ts.map +1 -0
  197. package/dist/qml/states.js +276 -0
  198. package/dist/qml/states.js.map +1 -0
  199. package/dist/qml/view-utils.d.ts +42 -0
  200. package/dist/qml/view-utils.d.ts.map +1 -0
  201. package/dist/qml/view-utils.js +121 -0
  202. package/dist/qml/view-utils.js.map +1 -0
  203. package/dist/reorder/hx.d.ts +18 -0
  204. package/dist/reorder/hx.d.ts.map +1 -0
  205. package/dist/reorder/hx.js +10 -0
  206. package/dist/reorder/hx.js.map +1 -0
  207. package/dist/reorder/index.d.ts +314 -0
  208. package/dist/reorder/index.d.ts.map +1 -0
  209. package/dist/reorder/index.js +1539 -0
  210. package/dist/reorder/index.js.map +1 -0
  211. package/dist/reorder/model.d.ts +107 -0
  212. package/dist/reorder/model.d.ts.map +1 -0
  213. package/dist/reorder/model.js +162 -0
  214. package/dist/reorder/model.js.map +1 -0
  215. package/dist/richtext/node.d.ts +15 -5
  216. package/dist/richtext/node.d.ts.map +1 -1
  217. package/dist/richtext/node.js +40 -11
  218. package/dist/richtext/node.js.map +1 -1
  219. package/dist/richtext/runs.d.ts +35 -13
  220. package/dist/richtext/runs.d.ts.map +1 -1
  221. package/dist/richtext/runs.js +83 -27
  222. package/dist/richtext/runs.js.map +1 -1
  223. package/dist/tabs/hx.d.ts +18 -0
  224. package/dist/tabs/hx.d.ts.map +1 -0
  225. package/dist/tabs/hx.js +10 -0
  226. package/dist/tabs/hx.js.map +1 -0
  227. package/dist/tabs/index.d.ts +160 -0
  228. package/dist/tabs/index.d.ts.map +1 -0
  229. package/dist/tabs/index.js +1265 -0
  230. package/dist/tabs/index.js.map +1 -0
  231. package/dist/terminal/vt/fonts.d.ts +45 -15
  232. package/dist/terminal/vt/fonts.d.ts.map +1 -1
  233. package/dist/terminal/vt/fonts.js +89 -19
  234. package/dist/terminal/vt/fonts.js.map +1 -1
  235. package/dist/terminal/vt/index.d.ts +1 -1
  236. package/dist/terminal/vt/index.d.ts.map +1 -1
  237. package/dist/terminal/vt/index.js +3 -3
  238. package/dist/terminal/vt/index.js.map +1 -1
  239. package/dist/terminal/vt/node.d.ts +42 -0
  240. package/dist/terminal/vt/node.d.ts.map +1 -1
  241. package/dist/terminal/vt/node.js +95 -11
  242. package/dist/terminal/vt/node.js.map +1 -1
  243. package/dist/terminal/vt/pty.d.ts +16 -0
  244. package/dist/terminal/vt/pty.d.ts.map +1 -1
  245. package/dist/terminal/vt/pty.js +186 -0
  246. package/dist/terminal/vt/pty.js.map +1 -1
  247. package/dist/terminal/vt/renderer.d.ts.map +1 -1
  248. package/dist/terminal/vt/renderer.js +13 -3
  249. package/dist/terminal/vt/renderer.js.map +1 -1
  250. package/package.json +23 -3
  251. package/src/charts/components.ts +14 -7
  252. package/src/charts/node.ts +81 -26
  253. package/src/charts/render.ts +16 -6
  254. package/src/code-editor/node.ts +147 -53
  255. package/src/flow/draw.ts +104 -35
  256. package/src/flow/index.ts +33 -13
  257. package/src/flow/model.ts +4 -2
  258. package/src/flow/node.ts +158 -59
  259. package/src/flow/types.ts +46 -21
  260. package/src/formula/index.ts +4 -2
  261. package/src/formula/node.ts +24 -5
  262. package/src/html/controls.ts +4 -1
  263. package/src/html/css/cascade.ts +18 -7
  264. package/src/html/css/parse.ts +46 -21
  265. package/src/html/css/style.ts +6 -1
  266. package/src/html/css/values.ts +25 -9
  267. package/src/html/index.ts +1 -0
  268. package/src/html/layout/boxes.ts +19 -9
  269. package/src/html/layout/inline.ts +23 -1
  270. package/src/html/node.ts +110 -17
  271. package/src/html/paint.ts +17 -2
  272. package/src/index.ts +142 -0
  273. package/src/maps/clip.ts +184 -0
  274. package/src/maps/gzip.ts +66 -0
  275. package/src/maps/index.ts +316 -0
  276. package/src/maps/labels.ts +442 -0
  277. package/src/maps/mvt.ts +857 -0
  278. package/src/maps/node.ts +1819 -0
  279. package/src/maps/overlay.ts +639 -0
  280. package/src/maps/paint.ts +978 -0
  281. package/src/maps/proj.ts +641 -0
  282. package/src/maps/sources.ts +425 -0
  283. package/src/maps/style.ts +309 -0
  284. package/src/maps/styles.ts +1040 -0
  285. package/src/maps/tiles.ts +676 -0
  286. package/src/maps/types.ts +260 -0
  287. package/src/qml/containers.tsx +380 -0
  288. package/src/qml/controls.tsx +47 -0
  289. package/src/qml/globals.ts +33 -0
  290. package/src/qml/index.ts +67 -0
  291. package/src/qml/interaction.tsx +317 -0
  292. package/src/qml/ir.ts +80 -0
  293. package/src/qml/layouts.tsx +141 -0
  294. package/src/qml/models.ts +101 -0
  295. package/src/qml/objects.ts +1431 -0
  296. package/src/qml/parse.ts +624 -0
  297. package/src/qml/qtquick.tsx +413 -0
  298. package/src/qml/react.tsx +454 -0
  299. package/src/qml/resolver.ts +99 -0
  300. package/src/qml/slots.ts +249 -0
  301. package/src/qml/states.ts +318 -0
  302. package/src/qml/view-utils.ts +140 -0
  303. package/src/reorder/hx.ts +31 -0
  304. package/src/reorder/index.ts +2223 -0
  305. package/src/reorder/model.ts +232 -0
  306. package/src/richtext/node.ts +49 -14
  307. package/src/richtext/runs.ts +115 -30
  308. package/src/tabs/hx.ts +31 -0
  309. package/src/tabs/index.ts +1686 -0
  310. package/src/terminal/vt/fonts.ts +128 -36
  311. package/src/terminal/vt/index.ts +4 -2
  312. package/src/terminal/vt/node.ts +105 -12
  313. package/src/terminal/vt/pty.ts +243 -0
  314. package/src/terminal/vt/renderer.ts +13 -3
@@ -0,0 +1,33 @@
1
+ // Host globals, reached structurally through `globalThis`. The build
2
+ // compiles `src/` with `types: []` on purpose — a Node global that wanders
3
+ // in must fail the build rather than become an implicit `@types/node`
4
+ // dependency (AGENTS.md) — so the few platform facilities the QML runtime
5
+ // needs are typed here, the way `charts/timers.ts` and `three/globals.ts`
6
+ // do it.
7
+
8
+ interface HostGlobals {
9
+ queueMicrotask?(fn: () => void): void;
10
+ setTimeout?(fn: () => void, ms: number): unknown;
11
+ clearTimeout?(id: unknown): void;
12
+ console?: { warn(...args: unknown[]): void };
13
+ }
14
+
15
+ const g = globalThis as HostGlobals;
16
+
17
+ export function microtask(fn: () => void): void {
18
+ if (g.queueMicrotask) g.queueMicrotask(fn);
19
+ else void Promise.resolve().then(fn);
20
+ }
21
+
22
+ /** `Timer { interval: … }` — null when the host has no timer at all. */
23
+ export function schedule(fn: () => void, ms: number): unknown {
24
+ return g.setTimeout ? g.setTimeout(fn, ms) : null;
25
+ }
26
+
27
+ export function cancel(id: unknown): void {
28
+ if (id != null) g.clearTimeout?.(id);
29
+ }
30
+
31
+ export function warn(...args: unknown[]): void {
32
+ g.console?.warn(...args);
33
+ }
@@ -0,0 +1,67 @@
1
+ // `@react-x11/components/qml` — QML as an authoring layer over react-x11.
2
+ //
3
+ // A zero-dependency QML engine: the parser, the reactive slot graph with
4
+ // QML's binding-breaking assignment, the object model with Qt's scoping
5
+ // rules, and a QtQuick subset whose every pixel is an ordinary
6
+ // `<box>`/`<text>`/`<image>` committed through the renderer. The docs page
7
+ // is docs/components/qml.md; the design record is react-x11's QML RFC.
8
+ //
9
+ // Importing this module populates the QtQuick registry — the family's own
10
+ // module-level registry, no core state touched. That is this family's one
11
+ // import-time side effect, the same shape as a component registering its
12
+ // element (AGENTS.md, "Tree-shaking"): it lives here so a bundler that
13
+ // keeps the family runs it, and one that shakes the family drops it.
14
+
15
+ import { registerQtQuick } from './qtquick.js';
16
+
17
+ registerQtQuick();
18
+
19
+ export { parseQml, ParseError } from './parse.js';
20
+ export type {
21
+ QmlDocument,
22
+ QmlImport,
23
+ ObjectIR,
24
+ MemberIR,
25
+ ValueIR,
26
+ } from './ir.js';
27
+ export {
28
+ Qt,
29
+ QmlInstance,
30
+ Context,
31
+ instantiateDocument,
32
+ instantiateTemplate,
33
+ migrateUserState,
34
+ registerQmlModule,
35
+ instanceOf,
36
+ lookupType,
37
+ } from './objects.js';
38
+ export type {
39
+ QmlFacade,
40
+ QmlTypeDef,
41
+ QmlTypeInfo,
42
+ QmlPropertyDef,
43
+ ValueSourceHookup,
44
+ InstantiateResult,
45
+ QtNamespace,
46
+ TemplateRef,
47
+ } from './objects.js';
48
+ export { Slot, flushBindings } from './slots.js';
49
+ export {
50
+ QmlView,
51
+ QmlNode,
52
+ registerReactComponent,
53
+ renderQmlChildren,
54
+ useQmlVersion,
55
+ } from './react.js';
56
+ export type {
57
+ QmlViewProps,
58
+ QmlViewHandle,
59
+ RegisterReactComponentOptions,
60
+ } from './react.js';
61
+ export { qmlColor, registerQtQuick } from './qtquick.js';
62
+ export { createFileResolver } from './resolver.js';
63
+ export type { QmlResolver, DocLoad } from './resolver.js';
64
+ export { geometryStyle, preferredSpan } from './react.js';
65
+ export { captureNode, hostNode } from './view-utils.js';
66
+ export { registerControls } from './controls.js';
67
+ export type { ControlsWidgets } from './controls.js';
@@ -0,0 +1,317 @@
1
+ // Pointer and keyboard: MouseArea, the `Keys` attached handlers, `focus`/
2
+ // `activeFocus`, and the two text editors over core's `<textinput>` /
3
+ // `<textarea>`.
4
+
5
+ import { useEffect } from 'react';
6
+ import type { ReactElement } from 'react';
7
+ import type { Style } from 'react-x11/style';
8
+ import {
9
+ XK_BACKSPACE,
10
+ XK_DELETE,
11
+ XK_DOWN,
12
+ XK_ESCAPE,
13
+ XK_KP_ENTER,
14
+ XK_LEFT,
15
+ XK_RETURN,
16
+ XK_RIGHT,
17
+ XK_SPACE,
18
+ XK_TAB,
19
+ XK_UP,
20
+ } from 'react-x11/keysyms';
21
+
22
+ import { QmlInstance, type QmlTypeDef } from './objects.js';
23
+ import { renderQmlChildren, geometryStyle } from './react.js';
24
+ import {
25
+ applyMotion,
26
+ captureNode,
27
+ hostNode,
28
+ num,
29
+ qmlColor,
30
+ str,
31
+ } from './view-utils.js';
32
+
33
+ // --- Keys + focus ----------------------------------------------------------
34
+
35
+ interface KeyEventLike {
36
+ keysym?: number;
37
+ key?: string;
38
+ shiftKey?: boolean;
39
+ ctrlKey?: boolean;
40
+ altKey?: boolean;
41
+ metaKey?: boolean;
42
+ stopPropagation?(): void;
43
+ }
44
+
45
+ interface PointerEventLike {
46
+ x: number;
47
+ y: number;
48
+ button?: number;
49
+ detail?: number;
50
+ shiftKey?: boolean;
51
+ ctrlKey?: boolean;
52
+ altKey?: boolean;
53
+ stopPropagation?(): void;
54
+ }
55
+
56
+ interface WheelEventLike extends PointerEventLike {
57
+ deltaX?: number;
58
+ deltaY?: number;
59
+ }
60
+
61
+ // Qt's modifier flags, so `mouse.modifiers & Qt.ShiftModifier` reads as
62
+ // ported code expects.
63
+ const SHIFT = 0x02000000;
64
+ const CTRL = 0x04000000;
65
+ const ALT = 0x08000000;
66
+ const META = 0x10000000;
67
+
68
+ const modifiersOf = (e: {
69
+ shiftKey?: boolean;
70
+ ctrlKey?: boolean;
71
+ altKey?: boolean;
72
+ metaKey?: boolean;
73
+ }): number =>
74
+ (e.shiftKey ? SHIFT : 0) |
75
+ (e.ctrlKey ? CTRL : 0) |
76
+ (e.altKey ? ALT : 0) |
77
+ (e.metaKey ? META : 0);
78
+
79
+ const NAMED_KEYS: Record<number, string> = {
80
+ [XK_RETURN]: 'Return',
81
+ [XK_KP_ENTER]: 'Enter',
82
+ [XK_ESCAPE]: 'Escape',
83
+ [XK_UP]: 'Up',
84
+ [XK_DOWN]: 'Down',
85
+ [XK_LEFT]: 'Left',
86
+ [XK_RIGHT]: 'Right',
87
+ [XK_TAB]: 'Tab',
88
+ [XK_SPACE]: 'Space',
89
+ [XK_DELETE]: 'Delete',
90
+ [XK_BACKSPACE]: 'Back',
91
+ };
92
+
93
+ function dispatchKeys(inst: QmlInstance, e: KeyEventLike): void {
94
+ const ev = {
95
+ key: e.keysym ?? 0,
96
+ text: e.key ?? '',
97
+ modifiers: modifiersOf(e),
98
+ accepted: false,
99
+ };
100
+ const named = NAMED_KEYS[ev.key];
101
+ if (named) inst.attachedHandlers.get(`Keys.on${named}Pressed`)?.(ev);
102
+ if (!ev.accepted) inst.attachedHandlers.get('Keys.onPressed')?.(ev);
103
+ if (ev.accepted) e.stopPropagation?.();
104
+ }
105
+
106
+ /**
107
+ * The focus/keyboard props an Item earns from `focus: true`, `Keys.*`
108
+ * handlers, or being told to be focusable. `activeFocus` follows the real
109
+ * focus events.
110
+ */
111
+ export function keyFocusProps(inst: QmlInstance): Record<string, unknown> {
112
+ const wantsFocus = inst.slots.get('focus')?.peek() === true;
113
+ let hasKeys = false;
114
+ for (const k of inst.attachedHandlers.keys()) {
115
+ if (k.startsWith('Keys.')) {
116
+ hasKeys = true;
117
+ break;
118
+ }
119
+ }
120
+ if (!wantsFocus && !hasKeys) return {};
121
+ const props: Record<string, unknown> = {
122
+ focusable: true,
123
+ onFocus: () => inst.slots.get('activeFocus')?.assign(true),
124
+ onBlur: () => inst.slots.get('activeFocus')?.assign(false),
125
+ };
126
+ if (wantsFocus) props.autoFocus = true;
127
+ if (hasKeys) props.onKeyDown = (e: KeyEventLike) => dispatchKeys(inst, e);
128
+ return props;
129
+ }
130
+
131
+ // --- MouseArea -------------------------------------------------------------
132
+
133
+ function MouseAreaView({ inst }: { inst: QmlInstance }): ReactElement {
134
+ const enabled = inst.slot('enabled').peek() !== false;
135
+ const hoverEnabled = inst.slot('hoverEnabled').peek() === true;
136
+ const style = geometryStyle(inst);
137
+ applyMotion(inst, style);
138
+
139
+ const mouse = (e: PointerEventLike) => {
140
+ const abs = hostNode(inst)?.abs;
141
+ return {
142
+ x: e.x - (abs?.x ?? 0),
143
+ y: e.y - (abs?.y ?? 0),
144
+ button: e.button,
145
+ modifiers: modifiersOf(e),
146
+ accepted: true,
147
+ };
148
+ };
149
+
150
+ const handlers: Record<string, unknown> = {};
151
+ if (enabled) {
152
+ handlers.onClick = (e: PointerEventLike) => {
153
+ e.stopPropagation?.();
154
+ inst.emit('clicked', mouse(e));
155
+ if (e.detail === 2) inst.emit('doubleClicked', mouse(e));
156
+ };
157
+ handlers.onMouseDown = (e: PointerEventLike) => {
158
+ e.stopPropagation?.();
159
+ inst.slot('pressed').assign(true);
160
+ inst.emit('pressed', mouse(e));
161
+ };
162
+ handlers.onMouseUp = (e: PointerEventLike) => {
163
+ e.stopPropagation?.();
164
+ inst.slot('pressed').assign(false);
165
+ inst.emit('released', mouse(e));
166
+ };
167
+ handlers.onMouseMove = (e: PointerEventLike) => {
168
+ inst.emit('positionChanged', mouse(e));
169
+ };
170
+ handlers.onWheel = (e: WheelEventLike) => {
171
+ inst.emit('wheel', {
172
+ ...mouse(e),
173
+ angleDelta: { x: e.deltaX ?? 0, y: e.deltaY ?? 0 },
174
+ });
175
+ };
176
+ if (hoverEnabled) {
177
+ handlers.onMouseEnter = () => {
178
+ inst.slot('containsMouse').assign(true);
179
+ inst.emit('entered');
180
+ };
181
+ handlers.onMouseLeave = () => {
182
+ inst.slot('containsMouse').assign(false);
183
+ inst.emit('exited');
184
+ };
185
+ }
186
+ }
187
+
188
+ return (
189
+ <box
190
+ ref={captureNode(inst)}
191
+ style={style}
192
+ {...handlers}
193
+ {...keyFocusProps(inst)}
194
+ >
195
+ {renderQmlChildren(inst)}
196
+ </box>
197
+ );
198
+ }
199
+
200
+ // --- TextInput / TextEdit --------------------------------------------------
201
+
202
+ /** The font/colour subset Text and the editors share. */
203
+ export function applyFontStyle(inst: QmlInstance, style: Style): void {
204
+ const color = inst.slots.get('color')?.peek();
205
+ if (color !== undefined) style.color = qmlColor(color);
206
+ const px = num(inst.slots.get('font.pixelSize')?.peek());
207
+ const pt = num(inst.slots.get('font.pointSize')?.peek());
208
+ if (px) style.fontSize = px;
209
+ else if (pt) style.fontSize = Math.round((pt * 96) / 72); // CSS's 1pt
210
+ const family = inst.slots.get('font.family')?.peek();
211
+ if (family) style.fontFamily = String(family);
212
+ if (inst.slots.get('font.bold')?.peek()) style.fontWeight = 700;
213
+ else {
214
+ const weight = num(inst.slots.get('font.weight')?.peek());
215
+ if (weight) style.fontWeight = weight;
216
+ }
217
+ if (inst.slots.get('font.italic')?.peek()) style.fontStyle = 'italic';
218
+ }
219
+
220
+ function useImplicitLineHeight(inst: QmlInstance, text: string): void {
221
+ useEffect(() => {
222
+ const node = hostNode(inst) as {
223
+ app?: { fonts?: { layout(t: string, s: unknown): { height: number } } };
224
+ resolvedTextStyle?(): unknown;
225
+ } | null;
226
+ if (!node?.app?.fonts || !node.resolvedTextStyle) return;
227
+ try {
228
+ const probe = node.app.fonts.layout(
229
+ text || 'Mg',
230
+ node.resolvedTextStyle(),
231
+ );
232
+ inst.slots.get('implicitHeight')?.assign(Math.ceil(probe.height) + 8);
233
+ } catch {
234
+ // No fonts loaded (headless without the fonts option): leave 0.
235
+ }
236
+ });
237
+ }
238
+
239
+ function editorView(tag: 'textinput' | 'textarea') {
240
+ return function EditorView({ inst }: { inst: QmlInstance }): ReactElement {
241
+ const style = geometryStyle(inst);
242
+ applyFontStyle(inst, style);
243
+ applyMotion(inst, style);
244
+ const text = str(inst.slot('text').peek());
245
+ useImplicitLineHeight(inst, text);
246
+ const maxLen = num(inst.slots.get('maximumLength')?.peek());
247
+ const props: Record<string, unknown> = {
248
+ ref: captureNode(inst),
249
+ style,
250
+ value: text,
251
+ onChange: (ev: { value: string }) => inst.slot('text').set(ev.value),
252
+ ...keyFocusProps(inst),
253
+ };
254
+ if (tag === 'textinput') {
255
+ props.onSubmit = () => inst.emit('accepted');
256
+ if (maxLen > 0 && maxLen < 32767) props.maxLength = maxLen;
257
+ return <textinput {...props} />;
258
+ }
259
+ return <textarea {...props} />;
260
+ };
261
+ }
262
+
263
+ // --- the type definitions --------------------------------------------------
264
+
265
+ export const interactionTypes: Record<string, QmlTypeDef> = {
266
+ MouseArea: {
267
+ extends: 'Item',
268
+ view: MouseAreaView,
269
+ properties: {
270
+ pressed: { default: false },
271
+ containsMouse: { default: false },
272
+ hoverEnabled: { default: false },
273
+ },
274
+ signals: {
275
+ clicked: ['mouse'],
276
+ doubleClicked: ['mouse'],
277
+ pressed: ['mouse'],
278
+ released: ['mouse'],
279
+ positionChanged: ['mouse'],
280
+ entered: [],
281
+ exited: [],
282
+ wheel: ['wheel'],
283
+ },
284
+ },
285
+
286
+ TextInput: {
287
+ extends: 'Item',
288
+ view: editorView('textinput'),
289
+ properties: {
290
+ text: { default: '' },
291
+ color: { default: undefined },
292
+ maximumLength: { default: 32767 },
293
+ 'font.pixelSize': { default: 0 },
294
+ 'font.pointSize': { default: 0 },
295
+ 'font.family': { default: '' },
296
+ 'font.bold': { default: false },
297
+ 'font.italic': { default: false },
298
+ 'font.weight': { default: 0 },
299
+ },
300
+ signals: { accepted: [], editingFinished: [] },
301
+ },
302
+
303
+ TextEdit: {
304
+ extends: 'Item',
305
+ view: editorView('textarea'),
306
+ properties: {
307
+ text: { default: '' },
308
+ color: { default: undefined },
309
+ 'font.pixelSize': { default: 0 },
310
+ 'font.pointSize': { default: 0 },
311
+ 'font.family': { default: '' },
312
+ 'font.bold': { default: false },
313
+ 'font.italic': { default: false },
314
+ 'font.weight': { default: 0 },
315
+ },
316
+ },
317
+ };
package/src/qml/ir.ts ADDED
@@ -0,0 +1,80 @@
1
+ // The intermediate representation both halves share: the parser produces it,
2
+ // the runtime instantiates it, and the future compiler will serialize it.
3
+ // Everything is plain data — no methods, no cycles — so a document parses
4
+ // once and instantiates many times (a Repeater's delegate is one ObjectIR,
5
+ // however many rows exist).
6
+
7
+ export interface QmlImport {
8
+ /** `import QtQuick 2.15` — a registry module name. */
9
+ module?: string;
10
+ /** `import "./widgets"` — a path import (resolved by the host app). */
11
+ path?: string;
12
+ version?: string;
13
+ alias?: string;
14
+ }
15
+
16
+ export interface ObjectIR {
17
+ type: string;
18
+ id: string | null;
19
+ members: MemberIR[];
20
+ loc: number;
21
+ }
22
+
23
+ export type ValueIR =
24
+ /** A JavaScript expression, compiled lazily by the runtime. */
25
+ | { kind: 'expr'; src: string; loc: number }
26
+ /** `prop: { …statements }` — a script binding (still reactive). */
27
+ | { kind: 'block'; src: string; loc: number }
28
+ /** `delegate: Component { … }`, `background: Rectangle { … }`. */
29
+ | { kind: 'object'; object: ObjectIR }
30
+ /** `states: [State { … }, …]` — an array of object templates. */
31
+ | { kind: 'array'; items: ValueIR[] };
32
+
33
+ export interface AliasTargetIR {
34
+ kind: 'alias-target';
35
+ path: string[];
36
+ }
37
+
38
+ export type MemberIR =
39
+ | {
40
+ kind: 'property';
41
+ name: string;
42
+ propType: string;
43
+ readonly: boolean;
44
+ default: boolean;
45
+ required: boolean;
46
+ value: ValueIR | AliasTargetIR | null;
47
+ loc: number;
48
+ }
49
+ | {
50
+ kind: 'signal';
51
+ name: string;
52
+ params: Array<{ type: string; name: string }>;
53
+ loc: number;
54
+ }
55
+ | {
56
+ kind: 'function';
57
+ name: string;
58
+ params: string[];
59
+ body: string;
60
+ loc: number;
61
+ }
62
+ | { kind: 'binding'; path: string[]; value: ValueIR; loc: number }
63
+ | { kind: 'group'; bindings: BindingMemberIR[]; loc: number }
64
+ | { kind: 'object'; object: ObjectIR; loc: number }
65
+ | { kind: 'value-source'; object: ObjectIR; on: string[]; loc: number }
66
+ | { kind: 'id'; id: string; loc: number }
67
+ | { kind: 'inline-component'; name: string; object: ObjectIR; loc: number };
68
+
69
+ export type BindingMemberIR = Extract<MemberIR, { kind: 'binding' }>;
70
+
71
+ export interface QmlDocument {
72
+ fileName: string;
73
+ imports: QmlImport[];
74
+ pragmas: string[];
75
+ root: ObjectIR;
76
+ /** How this document was loaded — set by the engine when a resolver is
77
+ * in play, so the type names it uses can resolve `.qml` files relative
78
+ * to it (resolver.ts). */
79
+ load?: import('./resolver.js').DocLoad;
80
+ }
@@ -0,0 +1,141 @@
1
+ // QtQuick.Layouts — RowLayout and ColumnLayout, lowered onto yoga.
2
+ //
3
+ // This is react-x11's home turf: the container renders a flex `<box>` and
4
+ // every child renders as a flex item styled from its `Layout.*` attached
5
+ // properties (react.tsx, `layoutChildStyle`) — `fillWidth` is `flexGrow`,
6
+ // `preferredWidth` is the basis, `alignment` is `alignSelf`, margins are
7
+ // margins. Yoga decides the geometry; the renderer never re-implements it.
8
+ //
9
+ // What QML still needs from that arrangement is *readable geometry*:
10
+ // `item.x` and `item.width` are ordinary properties other bindings consume.
11
+ // After each layout pass the container reads its children's laid-out rects
12
+ // and `reflect()`s them into the x/y/width/height slots — a write that
13
+ // disturbs no binding and no flag, so the implicit-width default keeps
14
+ // meaning "content-sized" and an author `width:` keeps reading as a size
15
+ // hint. Expressions see yoga's answer; yoga sees the hints.
16
+ //
17
+ // GridLayout is not here yet — yoga has no grid, and a half-true one would
18
+ // be worse than the clear unknown-type error; RowLayout, ColumnLayout and
19
+ // the full Layout.* set cover the linked reference page.
20
+
21
+ import { useEffect } from 'react';
22
+ import type { ReactElement } from 'react';
23
+
24
+ import { QmlInstance, type QmlTypeDef } from './objects.js';
25
+ import { renderQmlChildren, geometryStyle, preferredSpan } from './react.js';
26
+ import {
27
+ applyMotion,
28
+ captureNode,
29
+ hostNode,
30
+ initItem,
31
+ num,
32
+ } from './view-utils.js';
33
+ import {
34
+ afterLayout,
35
+ cancelAfterLayout,
36
+ type LayoutTick,
37
+ } from '../internal/timers.js';
38
+
39
+ function isRow(inst: QmlInstance): boolean {
40
+ return inst.typeInfo.name === 'RowLayout';
41
+ }
42
+
43
+ /** Reflect yoga's answers into the managed children's geometry slots. */
44
+ function feedbackLayout(inst: QmlInstance): void {
45
+ if (inst.destroyed) return;
46
+ const containerAbs = hostNode(inst)?.abs;
47
+ if (!containerAbs) return;
48
+ for (const child of inst.visualChildren()) {
49
+ const abs = hostNode(child)?.abs;
50
+ if (!abs) continue; // a custom view without captureNode: skip quietly
51
+ child.slots.get('x')?.reflect(abs.x - containerAbs.x);
52
+ child.slots.get('y')?.reflect(abs.y - containerAbs.y);
53
+ child.slots.get('width')?.reflect(abs.width);
54
+ child.slots.get('height')?.reflect(abs.height);
55
+ }
56
+ }
57
+
58
+ function LayoutView({ inst }: { inst: QmlInstance }): ReactElement {
59
+ const horizontal = isRow(inst);
60
+ const style = geometryStyle(inst);
61
+ style.display = 'flex';
62
+ style.flexDirection = horizontal ? 'row' : 'column';
63
+ style.gap = num(inst.slot('spacing').peek());
64
+ applyMotion(inst, style);
65
+
66
+ // Feed geometry back after the layout flush — and again whenever any
67
+ // child re-renders (its size hints may have moved yoga). A reflect that
68
+ // changes nothing notifies nobody, so this settles.
69
+ useEffect(() => {
70
+ let tick: LayoutTick = null;
71
+ const schedule = (): void => {
72
+ cancelAfterLayout(tick);
73
+ tick = afterLayout(() => feedbackLayout(inst));
74
+ };
75
+ schedule();
76
+ const offs = inst.visualChildren().map((c) => c.subscribe(schedule));
77
+ return () => {
78
+ cancelAfterLayout(tick);
79
+ offs.forEach((off) => off());
80
+ };
81
+ });
82
+
83
+ return (
84
+ <box ref={captureNode(inst)} style={style}>
85
+ {renderQmlChildren(inst)}
86
+ </box>
87
+ );
88
+ }
89
+
90
+ /** The container's own size hint: sum of children's preferred spans on the
91
+ * main axis (plus spacing), max on the cross — tracked bindings, so a
92
+ * child's hint change resizes a content-sized layout. */
93
+ function layoutImplicit(inst: QmlInstance): void {
94
+ const horizontal = isRow(inst);
95
+ const tracked = (slot: { get(): unknown }): unknown => slot.get();
96
+ const mainExtent = (): number => {
97
+ let sum = 0;
98
+ let count = 0;
99
+ const spacing = num(inst.slot('spacing').get());
100
+ for (const child of inst.visualChildren()) {
101
+ if (child.slot('visible').get() === false) continue;
102
+ sum += preferredSpan(child, horizontal, tracked) ?? 0;
103
+ count++;
104
+ }
105
+ return sum + spacing * Math.max(0, count - 1);
106
+ };
107
+ const crossExtent = (): number => {
108
+ let max = 0;
109
+ for (const child of inst.visualChildren()) {
110
+ if (child.slot('visible').get() === false) continue;
111
+ max = Math.max(max, preferredSpan(child, !horizontal, tracked) ?? 0);
112
+ }
113
+ return max;
114
+ };
115
+ inst
116
+ .slot(horizontal ? 'implicitWidth' : 'implicitHeight')
117
+ .setBinding(mainExtent);
118
+ inst
119
+ .slot(horizontal ? 'implicitHeight' : 'implicitWidth')
120
+ .setBinding(crossExtent);
121
+ }
122
+
123
+ function layoutInit(inst: QmlInstance): void {
124
+ initItem(inst);
125
+ layoutImplicit(inst);
126
+ }
127
+
128
+ const layoutContainer: QmlTypeDef = {
129
+ extends: 'Item',
130
+ managesChildLayout: true,
131
+ view: LayoutView,
132
+ // Qt's RowLayout/ColumnLayout default spacing.
133
+ properties: { spacing: { default: 5 } },
134
+ init: layoutInit,
135
+ onStructure: layoutImplicit, // a Repeater spliced new children in
136
+ };
137
+
138
+ export const layoutTypes: Record<string, QmlTypeDef> = {
139
+ RowLayout: { ...layoutContainer },
140
+ ColumnLayout: { ...layoutContainer },
141
+ };