buninu-browser 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 (296) hide show
  1. package/LICENSES/jsmdcui.txt +22 -0
  2. package/README.md +40 -4
  3. package/frontends/wayland/LICENSE-land +21 -0
  4. package/frontends/wayland/README.md +152 -0
  5. package/frontends/wayland/casty/LICENSE +21 -0
  6. package/frontends/wayland/casty/bookmarks.js +32 -0
  7. package/frontends/wayland/casty/click.js +96 -0
  8. package/frontends/wayland/casty/commands.js +61 -0
  9. package/frontends/wayland/casty/config.js +45 -0
  10. package/frontends/wayland/casty/download-directory.js +28 -0
  11. package/frontends/wayland/casty/download.js +49 -0
  12. package/frontends/wayland/casty/hints.js +334 -0
  13. package/frontends/wayland/casty/keys.js +56 -0
  14. package/frontends/wayland/casty/page-commands.js +80 -0
  15. package/frontends/wayland/casty/prompt-width.js +148 -0
  16. package/frontends/wayland/casty/urlbar.js +606 -0
  17. package/frontends/wayland/client.js +88 -0
  18. package/frontends/wayland/clipboard.js +80 -0
  19. package/frontends/wayland/index.js +130 -0
  20. package/frontends/wayland/input.js +75 -0
  21. package/frontends/wayland/protocol.js +55 -0
  22. package/frontends/wayland/protocols/wayland.xml +3248 -0
  23. package/frontends/wayland/protocols/xdg-shell.xml +1415 -0
  24. package/frontends/wayland/transport.js +120 -0
  25. package/frontends/wayland/urlbar.js +162 -0
  26. package/index.js +28 -1
  27. package/lib/audio/shared-connection.js +31 -0
  28. package/lib/cdp-server.js +15 -2
  29. package/lib/clipboard-command.js +10 -0
  30. package/lib/clipboard-powershell.js +22 -0
  31. package/lib/clipboard.js +37 -2
  32. package/lib/document-types.js +11 -0
  33. package/lib/headless-shell.js +54 -12
  34. package/lib/layout/text-layout.js +17 -9
  35. package/lib/paint/display-list.js +6 -1
  36. package/lib/qml/README.md +1427 -0
  37. package/lib/qml/builtins.js +771 -0
  38. package/lib/qml/canvas-dom.js +115 -0
  39. package/lib/qml/canvas.js +76 -0
  40. package/lib/qml/document.js +1199 -0
  41. package/lib/qml/fonts.js +69 -0
  42. package/lib/qml/format.js +9 -0
  43. package/lib/qml/host.js +352 -0
  44. package/lib/qml/images.js +199 -0
  45. package/lib/qml/launcher.js +132 -0
  46. package/lib/qml/loader.js +521 -0
  47. package/lib/qml/media-renderer.js +49 -0
  48. package/lib/qml/media.js +86 -0
  49. package/lib/qml/models.js +622 -0
  50. package/lib/qml/parser.js +381 -0
  51. package/lib/qml/qs +5 -0
  52. package/lib/qml/qscasty +3 -0
  53. package/lib/qml/qsheadless +3 -0
  54. package/lib/qml/qswayland +3 -0
  55. package/lib/qml/reactive.js +182 -0
  56. package/lib/qml/runtime.js +3603 -0
  57. package/lib/qml/shader/fragment.js +83 -0
  58. package/lib/qml/shader/qsb.js +27 -0
  59. package/lib/qml/shader/renderer.js +39 -0
  60. package/lib/qml/shader/runtime.js +61 -0
  61. package/lib/qml/shapes.js +251 -0
  62. package/lib/qml/shortcuts.js +65 -0
  63. package/lib/qml/text.js +109 -0
  64. package/lib/qml/textures.js +126 -0
  65. package/lib/qml/wayland-clipboard.js +32 -0
  66. package/lib/qml/window-constraints.js +16 -0
  67. package/lib/qml/worker-thread.js +70 -0
  68. package/lib/render-tree/index.js +16 -1
  69. package/lib/renderer/bindings.js +16 -1
  70. package/lib/renderer/canvas-2d.js +29 -0
  71. package/lib/renderer/document-selection.js +19 -0
  72. package/lib/renderer/host.js +45 -17
  73. package/lib/renderer/main.js +21 -6
  74. package/lib/renderer/page-realm.js +20 -10
  75. package/lib/renderer/page-renderer.js +497 -42
  76. package/lib/renderer/pointer-move.js +5 -3
  77. package/lib/renderer/render-interval.js +9 -0
  78. package/lib/renderer/text-input-paint.js +54 -0
  79. package/lib/renderer/text-input.js +57 -0
  80. package/lib/style/computed-style.js +10 -4
  81. package/package.json +1 -1
  82. package/test/audio-connection-lifetime.test.js +46 -0
  83. package/test/canvas-paint.test.js +8 -0
  84. package/test/clipboard.test.js +19 -0
  85. package/test/computed-style.test.js +12 -0
  86. package/test/document-selection.test.js +27 -0
  87. package/test/double-click.test.js +85 -0
  88. package/test/fixtures/canvas-damage.js +40 -0
  89. package/test/fixtures/fake-renderer.js +1 -0
  90. package/test/fixtures/flea-helpers/Dockerfile +36 -0
  91. package/test/fixtures/flea-helpers/bat.mjs +43 -0
  92. package/test/fixtures/flea-helpers/bin/bat +3 -0
  93. package/test/fixtures/flea-helpers/bin/bwrap +10 -0
  94. package/test/fixtures/flea-helpers/bin/bwrap-real-filter +9 -0
  95. package/test/fixtures/flea-helpers/bin/glow +3 -0
  96. package/test/fixtures/flea-helpers/bin/qs +3 -0
  97. package/test/fixtures/flea-helpers/bin/unsupported +3 -0
  98. package/test/fixtures/flea-helpers/check-highlighting.mjs +47 -0
  99. package/test/fixtures/flea-helpers/check-media.mjs +29 -0
  100. package/test/fixtures/flea-helpers/check-preview.mjs +27 -0
  101. package/test/fixtures/flea-helpers/check.mjs +36 -0
  102. package/test/fixtures/flea-helpers/highlight-cases.json +227 -0
  103. package/test/fixtures/flea-helpers/source-mime.xml +28 -0
  104. package/test/fixtures/flea-helpers/syntax-compat.mjs +23 -0
  105. package/test/fixtures/flea-thumbnail-stub/flea-placeholder.thumbnailer +4 -0
  106. package/test/fixtures/flea-thumbnail-stub/flea-thumbnail-stub +3 -0
  107. package/test/fixtures/omastorm-helpers/Dockerfile +35 -0
  108. package/test/fixtures/omastorm-helpers/check.mjs +56 -0
  109. package/test/fixtures/omastorm-helpers/launch.mjs +24 -0
  110. package/test/fixtures/omastorm-helpers/prepare.mjs +17 -0
  111. package/test/fixtures/omastorm-helpers/runtime.mjs +32 -0
  112. package/test/fixtures/pointer-paint-batching.js +23 -0
  113. package/test/fixtures/qml/flea/EscapeHint.qml +11 -0
  114. package/test/fixtures/qml/flea/LICENSE +21 -0
  115. package/test/fixtures/qml/flea/README.md +25 -0
  116. package/test/fixtures/qml/flea/Theme.qml +8 -0
  117. package/test/fixtures/qml/flea/demo.qml +24 -0
  118. package/test/fixtures/qml/flea/qmldir +3 -0
  119. package/test/fixtures/qml/flea-theme/LICENSE-Flea +21 -0
  120. package/test/fixtures/qml/flea-theme/LICENSE-Omarchy +20 -0
  121. package/test/fixtures/qml/flea-theme/README.md +70 -0
  122. package/test/fixtures/qml/flea-theme/button.qml +17 -0
  123. package/test/fixtures/qml/flea-theme/demo.qml +27 -0
  124. package/test/fixtures/qml/flea-theme/field.qml +21 -0
  125. package/test/fixtures/qml/flea-theme/imports/qs/Commons/BorderGeometry.js +373 -0
  126. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Color.qml +258 -0
  127. package/test/fixtures/qml/flea-theme/imports/qs/Commons/I18n.qml +118 -0
  128. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Numbers.js +19 -0
  129. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Plural.js +91 -0
  130. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Style.qml +561 -0
  131. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Util.qml +188 -0
  132. package/test/fixtures/qml/flea-theme/imports/qs/Commons/qmldir +13 -0
  133. package/test/fixtures/qml/flea-theme/segment.qml +17 -0
  134. package/test/fixtures/qml/flea-theme/settings.qml +58 -0
  135. package/test/fixtures/qml/flea-theme/text-metrics.qml +10 -0
  136. package/test/fixtures/qml/flea-theme/ui/DialogButton.qml +150 -0
  137. package/test/fixtures/qml/flea-theme/ui/DialogField.qml +147 -0
  138. package/test/fixtures/qml/flea-theme/ui/EscapeHint.qml +11 -0
  139. package/test/fixtures/qml/flea-theme/ui/Glass.qml +114 -0
  140. package/test/fixtures/qml/flea-theme/ui/Glyph.qml +54 -0
  141. package/test/fixtures/qml/flea-theme/ui/GlyphSolid.qml +22 -0
  142. package/test/fixtures/qml/flea-theme/ui/Reveal.qml +54 -0
  143. package/test/fixtures/qml/flea-theme/ui/SettingsRuler.qml +85 -0
  144. package/test/fixtures/qml/flea-theme/ui/SettingsSegment.qml +85 -0
  145. package/test/fixtures/qml/flea-theme/ui/StableModel.qml +89 -0
  146. package/test/fixtures/qml/flea-theme/ui/Theme.qml +460 -0
  147. package/test/fixtures/qml/flea-theme/ui/ThemeColors.qml +43 -0
  148. package/test/fixtures/qml/flea-theme/ui/ViewState.qml +565 -0
  149. package/test/fixtures/qml/flea-theme/ui/js/Anchor.js +379 -0
  150. package/test/fixtures/qml/flea-theme/ui/js/AnchorHold.js +162 -0
  151. package/test/fixtures/qml/flea-theme/ui/js/Archive.js +157 -0
  152. package/test/fixtures/qml/flea-theme/ui/js/BackReturn.js +110 -0
  153. package/test/fixtures/qml/flea-theme/ui/js/Buttons.js +54 -0
  154. package/test/fixtures/qml/flea-theme/ui/js/ColumnMenu.js +111 -0
  155. package/test/fixtures/qml/flea-theme/ui/js/Columns.js +298 -0
  156. package/test/fixtures/qml/flea-theme/ui/js/Contrast.js +157 -0
  157. package/test/fixtures/qml/flea-theme/ui/js/Density.js +44 -0
  158. package/test/fixtures/qml/flea-theme/ui/js/DirSizes.js +129 -0
  159. package/test/fixtures/qml/flea-theme/ui/js/ExtThumbs.js +115 -0
  160. package/test/fixtures/qml/flea-theme/ui/js/Filter.js +284 -0
  161. package/test/fixtures/qml/flea-theme/ui/js/FolderSorts.js +108 -0
  162. package/test/fixtures/qml/flea-theme/ui/js/Format.js +305 -0
  163. package/test/fixtures/qml/flea-theme/ui/js/Icons.js +206 -0
  164. package/test/fixtures/qml/flea-theme/ui/js/Input.js +39 -0
  165. package/test/fixtures/qml/flea-theme/ui/js/KeyName.js +64 -0
  166. package/test/fixtures/qml/flea-theme/ui/js/Keymap.js +370 -0
  167. package/test/fixtures/qml/flea-theme/ui/js/Kinds.js +145 -0
  168. package/test/fixtures/qml/flea-theme/ui/js/Lists.js +11 -0
  169. package/test/fixtures/qml/flea-theme/ui/js/MakeDefault.js +186 -0
  170. package/test/fixtures/qml/flea-theme/ui/js/Match.js +27 -0
  171. package/test/fixtures/qml/flea-theme/ui/js/Material.js +109 -0
  172. package/test/fixtures/qml/flea-theme/ui/js/Menu.js +513 -0
  173. package/test/fixtures/qml/flea-theme/ui/js/Motion.js +16 -0
  174. package/test/fixtures/qml/flea-theme/ui/js/Mounts.js +320 -0
  175. package/test/fixtures/qml/flea-theme/ui/js/MouseNav.js +13 -0
  176. package/test/fixtures/qml/flea-theme/ui/js/Nav.js +328 -0
  177. package/test/fixtures/qml/flea-theme/ui/js/Palette.js +33 -0
  178. package/test/fixtures/qml/flea-theme/ui/js/Places.js +236 -0
  179. package/test/fixtures/qml/flea-theme/ui/js/Protocols.js +296 -0
  180. package/test/fixtures/qml/flea-theme/ui/js/RecentMode.js +154 -0
  181. package/test/fixtures/qml/flea-theme/ui/js/Search.js +396 -0
  182. package/test/fixtures/qml/flea-theme/ui/js/Settings.js +476 -0
  183. package/test/fixtures/qml/flea-theme/ui/js/SettingsAbout.js +99 -0
  184. package/test/fixtures/qml/flea-theme/ui/js/SettingsShelf.js +52 -0
  185. package/test/fixtures/qml/flea-theme/ui/js/Sort.js +127 -0
  186. package/test/fixtures/qml/flea-theme/ui/js/StateWords.js +42 -0
  187. package/test/fixtures/qml/flea-theme/ui/js/Swap.js +166 -0
  188. package/test/fixtures/qml/flea-theme/ui/js/TextSize.js +107 -0
  189. package/test/fixtures/qml/flea-theme/ui/js/Thumbs.js +158 -0
  190. package/test/fixtures/qml/flea-theme/ui/js/UiState.js +438 -0
  191. package/test/fixtures/qml/flea-theme/ui/js/Update.js +132 -0
  192. package/test/fixtures/qml/flea-theme/ui/js/UserDirs.js +56 -0
  193. package/test/fixtures/qml/flea-theme/ui/qmldir +227 -0
  194. package/test/fixtures/qml/hello.qml +26 -0
  195. package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabel.qml +2 -0
  196. package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabelNew.qml +2 -0
  197. package/test/fixtures/qml/imports/Demo/Toolkit/qmldir +3 -0
  198. package/test/fixtures/qml/imports/components/Badge.qml +15 -0
  199. package/test/fixtures/qml/imports/logic/base.js +2 -0
  200. package/test/fixtures/qml/imports/logic/counter.js +4 -0
  201. package/test/fixtures/qml/imports/logic/math.js +3 -0
  202. package/test/fixtures/qml/imports/logic/shared.js +3 -0
  203. package/test/fixtures/qml/imports/main.qml +21 -0
  204. package/test/fixtures/qml/imports/theme/Theme.qml +8 -0
  205. package/test/fixtures/qml/imports/theme/qmldir +2 -0
  206. package/test/fixtures/qml/multiply.qml +61 -0
  207. package/test/fixtures/qml-canvas-dom.js +90 -0
  208. package/test/fixtures/qml-fonts/main.qml +10 -0
  209. package/test/fixtures/qml-locale/check.mjs +6 -0
  210. package/test/fixtures/qml-locale/main.qml +8 -0
  211. package/test/fixtures/qml-media-audio.js +39 -0
  212. package/test/fixtures/qml-shader/grab.qml +14 -0
  213. package/test/fixtures/qml-shader/gradient.frag +5 -0
  214. package/test/fixtures/qml-shader/main.qml +14 -0
  215. package/test/fixtures/qml-shader/texture-grab.qml +25 -0
  216. package/test/fixtures/qml-shader/texture-size.frag +5 -0
  217. package/test/fixtures/qml-shader-check.mjs +24 -0
  218. package/test/fixtures/title-no-paint.js +20 -0
  219. package/test/helpers/flea-sandbox.js +48 -0
  220. package/test/helpers/quickshell-fixture.js +125 -0
  221. package/test/html-clipboard-wayland.test.js +47 -0
  222. package/test/index.test.js +13 -0
  223. package/test/manual-casty-without-qml.js +87 -0
  224. package/test/manual-double-click.html +21 -0
  225. package/test/manual-flea-application.js +49 -0
  226. package/test/manual-flea-binary.js +62 -0
  227. package/test/manual-flea-qml.js +9 -0
  228. package/test/manual-flea-settings.js +40 -0
  229. package/test/manual-flea-theme.js +40 -0
  230. package/test/manual-mouse-buttons.html +18 -0
  231. package/test/manual-omastorm-engine.mjs +19 -0
  232. package/test/manual-omastorm-shaders.mjs +28 -0
  233. package/test/manual-omastorm-software.mjs +25 -0
  234. package/test/manual-pointer-scroll.html +43 -0
  235. package/test/manual-qml-removal.js +46 -0
  236. package/test/manual-without-qml.js +45 -0
  237. package/test/pointer-drag.test.js +104 -0
  238. package/test/qml-canvas-dom.test.js +37 -0
  239. package/test/qml-canvas.test.js +21 -0
  240. package/test/qml-clipboard-bridge.test.js +25 -0
  241. package/test/qml-clipboard-wayland.test.js +40 -0
  242. package/test/qml-connections-timing.test.js +95 -0
  243. package/test/qml-controls-window.test.js +182 -0
  244. package/test/qml-dynamic.test.js +401 -0
  245. package/test/qml-flea-app.test.js +146 -0
  246. package/test/qml-flea-backend.test.js +78 -0
  247. package/test/qml-flea-columns-backend.test.js +38 -0
  248. package/test/qml-flea-compile.test.js +78 -0
  249. package/test/qml-flea-launcher.test.js +88 -0
  250. package/test/qml-flea-markdown-backend.test.js +31 -0
  251. package/test/qml-flea-media.test.js +28 -0
  252. package/test/qml-flea-navigation-backend.test.js +35 -0
  253. package/test/qml-flea-navigation-timing.test.js +147 -0
  254. package/test/qml-flea-preview-backend.test.js +53 -0
  255. package/test/qml-flea-previews.test.js +163 -0
  256. package/test/qml-flea-rows.test.js +101 -0
  257. package/test/qml-flea-textures.test.js +59 -0
  258. package/test/qml-flea-view-switch.test.js +33 -0
  259. package/test/qml-flea-wayland.test.js +68 -0
  260. package/test/qml-flea.test.js +34 -0
  261. package/test/qml-fonts.test.js +31 -0
  262. package/test/qml-grid-multiline.test.js +48 -0
  263. package/test/qml-host.test.js +238 -0
  264. package/test/qml-imports.test.js +102 -0
  265. package/test/qml-inherited-handlers.test.js +51 -0
  266. package/test/qml-input-selection.test.js +66 -0
  267. package/test/qml-interactions.test.js +309 -0
  268. package/test/qml-language.test.js +167 -0
  269. package/test/qml-launcher.test.js +54 -0
  270. package/test/qml-layout-text.test.js +205 -0
  271. package/test/qml-locale.test.js +45 -0
  272. package/test/qml-media.test.js +63 -0
  273. package/test/qml-nonvisual-visible.test.js +19 -0
  274. package/test/qml-persisted-delegates.test.js +45 -0
  275. package/test/qml-quickshell-controls.test.js +65 -0
  276. package/test/qml-quickshell-settings.test.js +62 -0
  277. package/test/qml-repeater.test.js +72 -0
  278. package/test/qml-shader-runtime.test.js +6 -0
  279. package/test/qml-shortcuts.test.js +39 -0
  280. package/test/qml-socket.test.js +60 -0
  281. package/test/qml-software-shader.test.js +55 -0
  282. package/test/qml-style-updates.test.js +124 -0
  283. package/test/qml-textures.test.js +66 -0
  284. package/test/qml-views.test.js +332 -0
  285. package/test/qml-window-update.test.js +36 -0
  286. package/test/qml-worker.test.js +69 -0
  287. package/test/qml.test.js +83 -0
  288. package/test/render-interval.test.js +33 -0
  289. package/test/renderer-host.test.js +11 -0
  290. package/test/text-input-pointer.test.js +31 -0
  291. package/test/text-input-scroll.test.js +29 -0
  292. package/test/text-input.test.js +47 -0
  293. package/test/title-no-paint.test.js +6 -0
  294. package/test/wayland-frontend.test.js +83 -0
  295. package/test/wayland-scroll.test.js +40 -0
  296. package/test/wayland-urlbar.test.js +61 -0
@@ -0,0 +1,3603 @@
1
+ // Runs solely in page-realm.js. Flat DOM references and reactive values, not a
2
+ // retained QML AST/component tree. The page owns timers/listeners and disposal.
3
+ export function createQmlRuntime(
4
+ reactive,
5
+ Qt,
6
+ createQmlModels,
7
+ createQmlText,
8
+ createQmlShapes,
9
+ createQmlImages,
10
+ createQmlTextures,
11
+ createQmlMedia,
12
+ createQmlCanvas,
13
+ createQmlShortcuts,
14
+ createQmlShaderEffects,
15
+ ) {
16
+ const { untrack } = reactive;
17
+ // Qt.binding may also replace an unbound/default property (e.g. Item.visible).
18
+ // Existing binding/alias setters retain ownership of readonly and replacement rules.
19
+ const state = initial => reactive.state(initial, (proxy, target, key, value) => {
20
+ const read = Qt.bindingValue(value);
21
+ if (!read || Object.getOwnPropertyDescriptor(target, key)?.set) return false;
22
+ bind(proxy, key, read);
23
+ untrack(start);
24
+ return true;
25
+ });
26
+ const socketStates = new WeakMap();
27
+ let disposers = null;
28
+ const effect = (fn, options) => {
29
+ const stop = reactive.effect(fn, options);
30
+ disposers?.push(stop);
31
+ return stop;
32
+ };
33
+ const elements = new WeakMap(),
34
+ parents = new WeakMap(),
35
+ factories = new WeakMap(),
36
+ started = new WeakSet();
37
+ const propertyWrites = new WeakMap(),
38
+ aliases = new WeakMap();
39
+ const watchWrite = (target, key, callback) => {
40
+ let map = propertyWrites.get(target);
41
+ if (!map) propertyWrites.set(target, (map = new Map()));
42
+ map.set(key, callback);
43
+ const descriptor = Object.getOwnPropertyDescriptor(target, key);
44
+ if (!descriptor?.set) {
45
+ let cached = target[key];
46
+ Object.defineProperty(target, key, {
47
+ configurable: true,
48
+ enumerable: true,
49
+ get: () => cached,
50
+ set: (value) => {
51
+ cached = value;
52
+ callback();
53
+ },
54
+ });
55
+ }
56
+ };
57
+ const owners = new WeakMap(),
58
+ descriptions = new WeakMap(),
59
+ structure = state({ version: 0 });
60
+ const nonvisual = new Set([
61
+ 'MediaPlayer','AudioOutput','IdleInhibitor',
62
+ 'LinearGradient',
63
+ 'XmlListModel',
64
+ 'XmlListModelRole',
65
+ 'WorkerScript',
66
+ 'LoggingCategory',
67
+ 'ListModel',
68
+ 'DelegateModel',
69
+ 'DelegateModelGroup',
70
+ 'PointHandler',
71
+ 'Binding',
72
+ 'Gradient',
73
+ 'GradientStop',
74
+ 'IpcHandler',
75
+ 'DragHandler',
76
+ 'FrameAnimation',
77
+ 'Behavior',
78
+ 'NumberAnimation',
79
+ 'PropertyAnimation',
80
+ 'PauseAnimation',
81
+ 'SequentialAnimation',
82
+ 'ParallelAnimation',
83
+ 'ScriptAction',
84
+ 'PropertyAction',
85
+ 'Component',
86
+ 'ShellRoot',
87
+ 'Scale',
88
+ 'Translate',
89
+ 'Shortcut', 'Instantiator', 'Repeater',
90
+ 'TapHandler',
91
+ 'HoverHandler',
92
+ 'QtObject',
93
+ 'Singleton',
94
+ 'FileView',
95
+ 'Socket',
96
+ 'Process',
97
+ 'Timer',
98
+ 'Connections',
99
+ 'StdioCollector',
100
+ 'SplitParser',
101
+ 'TextMetrics',
102
+ 'FontMetrics',
103
+ ]);
104
+ const propertyName = (name) =>
105
+ name.replace(/^(_*)([A-Z])/, (_, prefix, c) => prefix + c.toLowerCase());
106
+ const childSets = new WeakMap();
107
+ const childSet = (parent) => {
108
+ if (!parent) return null;
109
+ let box = childSets.get(parent);
110
+ if (!box)
111
+ childSets.set(
112
+ parent,
113
+ (box = { items: new Set(), version: state({ value: 0 }) }),
114
+ );
115
+ return box;
116
+ };
117
+ const moveChild = (value, previous, next) => {
118
+ for (const [parent, adding] of [
119
+ [previous, false],
120
+ [next, true],
121
+ ]) {
122
+ const box = childSet(parent);
123
+ if (!box) continue;
124
+ if (adding) box.items.add(value);
125
+ else box.items.delete(value);
126
+ untrack(() => box.version.value++);
127
+ }
128
+ };
129
+ const childrenOf = (parent) => {
130
+ const box = childSet(parent);
131
+ if (!box) return [];
132
+ box.version.value;
133
+ return [...box.items].filter(
134
+ (value) => !nonvisual.has(owners.get(value)?.type),
135
+ );
136
+ };
137
+ const metricsCache = new Map(),
138
+ fontEpoch = state({ value: 0 });
139
+ let metricsContext;
140
+ const measureText = (font, text) => {
141
+ fontEpoch.value;
142
+ text = String(text);
143
+ const key = font + '\0' + text;
144
+ if (metricsCache.has(key)) return metricsCache.get(key);
145
+ metricsContext ??= document.createElement('canvas').getContext('2d');
146
+ metricsContext.font = font;
147
+ const measured = metricsContext.measureText(text);
148
+ // CanvasKit's empty paragraph can report a space advance; QML's empty
149
+ // string still has a font line box, but no horizontal ink or advance.
150
+ const metrics = text
151
+ ? measured
152
+ : {
153
+ ...measured,
154
+ width: 0,
155
+ actualBoundingBoxLeft: 0,
156
+ actualBoundingBoxRight: 0,
157
+ actualBoundingBoxAscent: 0,
158
+ actualBoundingBoxDescent: 0,
159
+ };
160
+ if (metricsCache.size >= 2048)
161
+ metricsCache.delete(metricsCache.keys().next().value);
162
+ if (text.length < 8192) metricsCache.set(key, metrics);
163
+ return metrics;
164
+ };
165
+ document.fonts?.addEventListener?.('loadingdone', () => {
166
+ metricsCache.clear();
167
+ fontEpoch.value++;
168
+ });
169
+ const margin = (v, edge) =>
170
+ v.anchors[edge + 'Margin'] ?? v.anchors.margins ?? 0;
171
+ const anchor = (v, line) => {
172
+ if (!line) return null;
173
+ const item = line.item,
174
+ parent = parents.get(v),
175
+ edge = line.edge;
176
+ const origin =
177
+ item === parent
178
+ ? 0
179
+ : edge === 'top' ||
180
+ edge === 'bottom' ||
181
+ edge === 'verticalCenter' ||
182
+ edge === 'baseline'
183
+ ? item.y
184
+ : item.x;
185
+ return (
186
+ origin +
187
+ ({
188
+ left: 0,
189
+ top: 0,
190
+ right: item.width,
191
+ bottom: item.height,
192
+ horizontalCenter: item.width / 2,
193
+ verticalCenter: item.height / 2,
194
+ baseline: item.baselineOffset,
195
+ }[edge] ?? 0)
196
+ );
197
+ };
198
+ // Layout preferred size: explicit hint, then positive implicit size, then
199
+ // the initial item size. Never feed a layout-assigned stretched size back
200
+ // into its own preference (Qt also caches this last-resort size hint).
201
+ const layoutFallbackSizes = new WeakMap();
202
+ const layoutPreferred = (item, suffix) => {
203
+ const hint = item.Layout['preferred' + suffix];
204
+ if (hint >= 0) return hint;
205
+ const implicit = item['implicit' + suffix];
206
+ if (implicit > 0) return implicit;
207
+ let saved = layoutFallbackSizes.get(item);
208
+ if (!saved) layoutFallbackSizes.set(item, saved = state({}));
209
+ if (!(suffix in saved)) {
210
+ const initial = untrack(() => item[suffix === 'Width' ? 'width' : 'height']);
211
+ saved[suffix] = Math.max(0, Number(initial) || 0);
212
+ }
213
+ return saved[suffix] || 0;
214
+ };
215
+
216
+ const dimension = (v, key, topLevel = false) => {
217
+ const a = v.anchors,
218
+ start = key === 'width' ? 'left' : 'top',
219
+ end = key === 'width' ? 'right' : 'bottom';
220
+ if (a.fill) return a.fill[key] - margin(v, start) - margin(v, end);
221
+ if (a[start] && a[end])
222
+ return (
223
+ anchor(v, a[end]) -
224
+ anchor(v, a[start]) -
225
+ margin(v, start) -
226
+ margin(v, end)
227
+ );
228
+ if (owners.get(v)?.type === 'FloatingWindow') return Math.max(v.minimumSize?.[key]||0,Math.min(v.maximumSize?.[key]>0?v.maximumSize[key]:Infinity,viewport[key]));
229
+ return (
230
+ v[key === 'width' ? 'implicitWidth' : 'implicitHeight'] ??
231
+ (topLevel ? viewport[key] : 0)
232
+ );
233
+ };
234
+ const position = (v, axis) => {
235
+ const a = v.anchors,
236
+ h = axis === 'x',
237
+ start = h ? 'left' : 'top',
238
+ end = h ? 'right' : 'bottom',
239
+ center = h ? 'horizontalCenter' : 'verticalCenter',
240
+ size = h ? v.width : v.height;
241
+ if (!h && a.baseline)
242
+ return anchor(v, a.baseline) - v.baselineOffset + (a.baselineOffset || 0);
243
+ if (a.fill) return margin(v, start);
244
+ if (a[start]) return anchor(v, a[start]) + margin(v, start);
245
+ if (a[end]) return anchor(v, a[end]) - margin(v, end) - size;
246
+ if (a.centerIn)
247
+ return (
248
+ ((h ? a.centerIn.width : a.centerIn.height) - size) / 2 +
249
+ (a[center + 'Offset'] || 0)
250
+ );
251
+ if (a[center])
252
+ return anchor(v, a[center]) - size / 2 + (a[center + 'Offset'] || 0);
253
+ return 0;
254
+ };
255
+ const listen = (element, name, fn) => {
256
+ element.addEventListener(name, fn);
257
+ disposers?.push(() => element.removeEventListener(name, fn));
258
+ };
259
+ // QML Items do not take keyboard focus merely because a pointer hits them.
260
+ // tabindex permits forceActiveFocus(), but HTML's implicit click focus must
261
+ // only apply to actual input controls; panel keyboard owners retain focus.
262
+ listen(document, 'mousedown', (event) => {
263
+ if (!event.target?.closest?.('input,textarea,select,button'))
264
+ event.preventDefault();
265
+ });
266
+ const enabled = (value) => {
267
+ for (let v = value; v; v = parents.get(v))
268
+ if (
269
+ !nonvisual.has(owners.get(v)?.type) &&
270
+ (v.enabled === false || v.visible === false)
271
+ )
272
+ return false;
273
+ return true;
274
+ };
275
+ const focusElements = new WeakMap(),
276
+ focusTargets = new WeakMap();
277
+ let activeFocusValues = new Set(),
278
+ activeWindow = null;
279
+ const isFocusScope = (value) =>
280
+ ['FocusScope', 'ListView', 'GridView'].includes(owners.get(value)?.type);
281
+ function activateFocus(value) {
282
+ const next = new Set();
283
+ if (value) {
284
+ next.add(value);
285
+ for (
286
+ let parent = parents.get(value);
287
+ parent;
288
+ parent = parents.get(parent)
289
+ ) {
290
+ if (isFocusScope(parent)) {
291
+ next.add(parent);
292
+ focusTargets.set(parent, value);
293
+ }
294
+ }
295
+ }
296
+ for (const item of activeFocusValues)
297
+ if (!next.has(item)) item.activeFocus = false;
298
+ for (const item of next) item.activeFocus = true;
299
+ activeFocusValues = next;
300
+ const window = value?.Window.window ?? null;
301
+ if (activeWindow && activeWindow !== window)
302
+ activeWindow.activeFocusItem = null;
303
+ if (window) window.activeFocusItem = value;
304
+ activeWindow = window;
305
+ }
306
+ function focusTarget(value) {
307
+ const selected = isFocusScope(value) && focusTargets.get(value);
308
+ if (selected && elements.get(selected)?.isConnected && enabled(selected)) {
309
+ for (let p = parents.get(selected); p; p = parents.get(p))
310
+ if (p === value) return selected;
311
+ }
312
+ return value;
313
+ }
314
+ const button = (event) =>
315
+ event.button === 2
316
+ ? 2
317
+ : event.button === 1
318
+ ? 4
319
+ : event.button >= 3 && event.button < 27
320
+ ? 2 ** event.button
321
+ : event.button === 0
322
+ ? 1
323
+ : 0;
324
+ const pointer = (value, element, event) => {
325
+ const rect = element.getBoundingClientRect();
326
+ return {
327
+ x: event.clientX - rect.x,
328
+ y: event.clientY - rect.y,
329
+ button: button(event),
330
+ buttons: event.buttons ?? 0,
331
+ modifiers:
332
+ (event.shiftKey ? Qt.ShiftModifier : 0) |
333
+ (event.ctrlKey ? Qt.ControlModifier : 0) |
334
+ (event.altKey ? Qt.AltModifier : 0) |
335
+ (event.metaKey ? Qt.MetaModifier : 0),
336
+ accepted: true,
337
+ };
338
+ };
339
+ const ipcHandlers = new Map();
340
+ const behaviors = new WeakMap(),
341
+ animationStops = new WeakMap();
342
+ let animationWrite = false;
343
+ const ease = (t, type) =>
344
+ type === 1
345
+ ? t * t
346
+ : type === 2
347
+ ? 1 - (1 - t) ** 2
348
+ : type === 3
349
+ ? t < 0.5
350
+ ? 2 * t * t
351
+ : 1 - (-2 * t + 2) ** 2 / 2
352
+ : type === 5
353
+ ? t ** 3
354
+ : type === 6
355
+ ? 1 - (1 - t) ** 3
356
+ : type === 7
357
+ ? t < 0.5
358
+ ? 4 * t ** 3
359
+ : 1 - (-2 * t + 2) ** 3 / 2
360
+ : type === 10
361
+ ? 1 - (1 - t) ** 4
362
+ : type === 14
363
+ ? 1 - (1 - t) ** 5
364
+ : type === 18
365
+ ? Math.sin((t * Math.PI) / 2)
366
+ : type === 22
367
+ ? t === 1
368
+ ? 1
369
+ : 1 - 2 ** (-10 * t)
370
+ : t;
371
+ const animate = (animation, done = () => {}, override = null) => {
372
+ animationStops.get(animation)?.();
373
+ let cancelled = false,
374
+ frame,
375
+ childStops = [],
376
+ repeat = 0;
377
+ const stop = () => {
378
+ cancelled = true;
379
+ if (frame !== undefined) cancelAnimationFrame(frame);
380
+ for (const stop of childStops) stop();
381
+ };
382
+ animationStops.set(animation, stop);
383
+ const type = owners.get(animation)?.type;
384
+ const cycle = () => {
385
+ if (cancelled) return;
386
+ const finish = () => {
387
+ if (cancelled) return;
388
+ if (animation.loops === -1 || ++repeat < (animation.loops || 1)) {
389
+ cycle();
390
+ return;
391
+ }
392
+ animationStops.delete(animation);
393
+ animation.running = false;
394
+ emit(animation, 'onFinished');
395
+ emit(animation, 'onStopped');
396
+ done();
397
+ };
398
+ if (type === 'SequentialAnimation' || type === 'ParallelAnimation') {
399
+ const children = nodes
400
+ .filter((n) => parents.get(n.value) === animation)
401
+ .map((n) => n.value);
402
+ let at = 0;
403
+ if (!children.length) {
404
+ finish();
405
+ return;
406
+ }
407
+ const next = () => {
408
+ if (at === children.length) {
409
+ finish();
410
+ return;
411
+ }
412
+ childStops.push(animate(children[at++], next));
413
+ };
414
+ if (type === 'SequentialAnimation') next();
415
+ else
416
+ for (const child of children)
417
+ childStops.push(
418
+ animate(child, () => {
419
+ if (++at === children.length) finish();
420
+ }),
421
+ );
422
+ return;
423
+ }
424
+ if (type === 'ScriptAction') {
425
+ emit(animation, 'script');
426
+ finish();
427
+ return;
428
+ }
429
+ if (type === 'PropertyAction') {
430
+ if (animation.target)
431
+ animation.target[animation.property] = animation.value;
432
+ finish();
433
+ return;
434
+ }
435
+ const targets = override
436
+ ? [override.target]
437
+ : (animation.targets?.length
438
+ ? animation.targets
439
+ : [animation.target]
440
+ ).filter(Boolean);
441
+ const keys = override
442
+ ? [override.key]
443
+ : String(animation.properties || animation.property || '')
444
+ .split(',')
445
+ .map((k) => k.trim())
446
+ .filter(Boolean);
447
+ const writes = targets.flatMap((target) =>
448
+ keys.map((key) => ({
449
+ target,
450
+ key,
451
+ from: override
452
+ ? override.from
453
+ : (animation.from ?? Number(target[key])),
454
+ to: override ? override.to : animation.to,
455
+ })),
456
+ );
457
+ const duration = Math.max(0, Number(animation.duration) || 0),
458
+ startedAt = performance.now();
459
+ const tick = (now) => {
460
+ if (cancelled) return;
461
+ const progress = duration
462
+ ? Math.min(1, (now - startedAt) / duration)
463
+ : 1,
464
+ t = ease(progress, animation.easing?.type);
465
+ animationWrite = true;
466
+ try {
467
+ for (const w of writes)
468
+ if (Number.isFinite(w.from) && Number.isFinite(w.to))
469
+ w.target[w.key] = w.from + (w.to - w.from) * t;
470
+ } finally {
471
+ animationWrite = false;
472
+ }
473
+ if (progress < 1) frame = requestAnimationFrame(tick);
474
+ else finish();
475
+ };
476
+ frame = requestAnimationFrame(tick);
477
+ };
478
+ cycle();
479
+ return stop;
480
+ };
481
+ const host = globalThis.__qmlHost;
482
+ const textContent = createQmlText(document, (source) => {
483
+ if (!host?.markdown)
484
+ throw Error('QML MarkdownText needs the local application host');
485
+ return host.markdown(source);
486
+ });
487
+ const sharedValues = new Map(),
488
+ scopedQt = new Map(),
489
+ components = new Map(),
490
+ later = new Map();
491
+ const destroyedNodes = new WeakSet();
492
+ function disposeScope(stops) {
493
+ const owned = nodes.filter((node) => node.scope === stops);
494
+ // Children have their own Component.onDestruction too. Emit while sibling
495
+ // state and bindings still exist; nested factories dispose their own scopes.
496
+ for (const node of owned)
497
+ if (!destroyedNodes.has(node.value)) {
498
+ destroyedNodes.add(node.value);
499
+ try {
500
+ emit(node.value, 'Component.onDestruction');
501
+ } catch (error) {
502
+ console.error('QML destruction handler:', error);
503
+ }
504
+ }
505
+ for (const stop of stops.splice(0).reverse()) {
506
+ try {
507
+ stop();
508
+ } catch (error) {
509
+ console.error('QML cleanup:', error);
510
+ }
511
+ }
512
+ for (const node of owned) {
513
+ moveChild(node.value, parents.get(node.value), null);
514
+ elements.get(node.value)?.remove();
515
+ const at = nodes.indexOf(node);
516
+ if (at >= 0) nodes.splice(at, 1);
517
+ }
518
+ untrack(() => structure.version++);
519
+ }
520
+ const instantiate = (factory, parent, properties = {}) => {
521
+ const previous = disposers,
522
+ stops = [],
523
+ pendingBefore = initial.length;
524
+ disposers = stops;
525
+ let value,
526
+ disposed = false;
527
+ const destroy = () => {
528
+ if (disposed) return;
529
+ disposed = true;
530
+ disposeScope(stops);
531
+ };
532
+ try {
533
+ // Imperative construction must not subscribe the caller binding to all
534
+ // properties read while initializing the newly created object.
535
+ value = untrack(() => factory(parent, properties));
536
+ value.destroy = (delay = 0) =>
537
+ delay > 0 ? setTimeout(destroy, delay) : destroy();
538
+ } catch (error) {
539
+ initial.splice(pendingBefore);
540
+ destroy();
541
+ throw error;
542
+ } finally {
543
+ disposers = previous;
544
+ }
545
+ // Ownership follows the requested parent, not whichever unrelated binding
546
+ // or Loader happened to invoke the factory during a reactive flush.
547
+ (owners.has(parent) ? owners.get(parent).scope : previous)?.push(destroy);
548
+ return value;
549
+ };
550
+ const qt = (base) => {
551
+ if (scopedQt.has(base)) return scopedQt.get(base);
552
+ const value = {
553
+ ...Qt,
554
+ openUrlExternally(url) {
555
+ if (!host) return false;
556
+ try {
557
+ host.run(['xdg-open', new URL(String(url), base).href], () => {});
558
+ return true;
559
+ } catch {
560
+ return false;
561
+ }
562
+ },
563
+ resolvedUrl: (path) => new URL(String(path), base).href,
564
+ callLater(fn, ...args) {
565
+ later.set(fn, args);
566
+ queueMicrotask(() => {
567
+ const args = later.get(fn);
568
+ if (args) {
569
+ later.delete(fn);
570
+ fn(...args);
571
+ }
572
+ });
573
+ },
574
+ createComponent(path) {
575
+ const url = new URL(String(path), base).href,
576
+ key = url + '\n' + [...sharedValues.keys()].sort().join('\n');
577
+ if (components.has(key)) return components.get(key);
578
+ let factory,
579
+ error = '',
580
+ compiledKeys = [...sharedValues.keys()].sort().join('\n');
581
+ try {
582
+ if (!host) throw Error('Dynamic local components need a QML host');
583
+ factory = new Function(
584
+ 'mount',
585
+ 'properties',
586
+ 'return ' + host.compile(url, [...sharedValues.keys()]),
587
+ );
588
+ } catch (e) {
589
+ error = String(e.message);
590
+ }
591
+ const result = {
592
+ status: factory ? 1 : 3,
593
+ errorString: () => error,
594
+ // Releasing a Component does not destroy objects it already created.
595
+ destroy() { if (components.get(key) === result) components.delete(key); },
596
+ createObject(parent, properties = {}) {
597
+ if (!factory) return null;
598
+ try {
599
+ const keys = [...sharedValues.keys()].sort().join('\n');
600
+ if (keys !== compiledKeys) {
601
+ factory = new Function(
602
+ 'mount',
603
+ 'properties',
604
+ 'return ' + host.compile(url, [...sharedValues.keys()]),
605
+ );
606
+ compiledKeys = keys;
607
+ }
608
+ return instantiate(factory, parent, properties);
609
+ } catch (e) {
610
+ error = String(e.message);
611
+ console.error('QML createObject: ' + (e.stack || error));
612
+ return null;
613
+ }
614
+ },
615
+ };
616
+ components.set(key, result);
617
+ return result;
618
+ },
619
+ };
620
+ scopedQt.set(base, value);
621
+ return value;
622
+ };
623
+ const Quickshell = {
624
+ env: (name) => host?.env(name) ?? null,
625
+ shellDir: host?.shellDir ?? '',
626
+ processId: host?.processId ?? 0,
627
+ execDetached: (cmd) => host.run(cmd, () => {}),
628
+ };
629
+ const nodes = [],
630
+ handlers = new WeakMap(),
631
+ baselines = new WeakMap(),
632
+ initial = [];
633
+ // Connections must follow a changed target before a same-handler emission.
634
+ // Drain only invalidated wiring, never unrelated layout/render effects.
635
+ const pendingConnections = new Set();
636
+ let connectingSignals = false;
637
+ const syncConnections = () => {
638
+ if (connectingSignals) return;
639
+ connectingSignals = true;
640
+ try {
641
+ let passes = 0;
642
+ while (pendingConnections.size) {
643
+ if (++passes > 100) throw Error('QML Connections binding cycle');
644
+ const batch = [...pendingConnections];
645
+ pendingConnections.clear();
646
+ for (const stop of batch) untrack(() => stop.run());
647
+ }
648
+ } finally { connectingSignals = false; }
649
+ };
650
+ const signal = () => {
651
+ const listeners = new Set();
652
+ const emit = (...args) => {
653
+ syncConnections();
654
+ for (const fn of [...listeners]) untrack(() => fn(...args));
655
+ };
656
+ Object.defineProperty(emit, 'listenerCount', { get: () => listeners.size });
657
+ emit.connect = (fn) => listeners.add(fn);
658
+ emit.disconnect = (fn) => listeners.delete(fn);
659
+ return emit;
660
+ };
661
+ const Hyprland = { rawEvent: signal() };
662
+ Quickshell.lastWindowClosed = signal();
663
+ const on = (value, name, fn) => {
664
+ // QML signal handlers execute with their declaring object as `this`,
665
+ // including handlers connected later or merged with inherited handlers.
666
+ fn = fn.bind(value);
667
+ if(name==='Window.onWindowChanged'){
668
+ initial.push(()=>effect(()=>{const window=value.Window.window;untrack(()=>fn(window));}));
669
+ return;
670
+ }
671
+ let map = handlers.get(value);
672
+ if (!map) handlers.set(value, (map = new Map()));
673
+ const previous = map.get(name);
674
+ map.set(name, previous ? (...args) => { previous(...args); fn(...args); } : fn);
675
+ if (name.endsWith('Changed') && /^on[A-Z_]/.test(name)) {
676
+ let values = baselines.get(value);
677
+ if (!values) baselines.set(value, (values = new Map()));
678
+ values.set(name, value[propertyName(name.slice(2, -7))]);
679
+ }
680
+ };
681
+ const emit = (value, name, ...args) =>
682
+ untrack(() => handlers.get(value)?.get(name)?.(...args));
683
+ const pendingPropertyHandlers = new Map();
684
+ const start = () => {
685
+ if (!nodes.some((node) => started.has(node.value)))
686
+ host?.hyprEvents((event) => Hyprland.rawEvent(event));
687
+ let pending = initial.splice(0),
688
+ last;
689
+ for (let pass = 0; pending.length && pass < 100; pass++) {
690
+ const retry = [],
691
+ revision = reactive.revision;
692
+ for (const init of pending) {
693
+ try {
694
+ init();
695
+ } catch (e) {
696
+ if (e.qmlFatal) throw e;
697
+ last = e;
698
+ retry.push(init);
699
+ }
700
+ }
701
+ const incomplete = reactive.flush(true);
702
+ if (incomplete) last = incomplete;
703
+ if (retry.length === pending.length && reactive.revision === revision)
704
+ throw last;
705
+ pending = retry;
706
+ }
707
+ if (pending.length) throw last;
708
+ reactive.flush();
709
+ for (const node of [...nodes])
710
+ if (node.paint && !node.painted) {
711
+ node.painted = true;
712
+ const previous = disposers;
713
+ disposers = node.scope;
714
+ try {
715
+ effect(node.paint);
716
+ } finally {
717
+ disposers = previous;
718
+ }
719
+ }
720
+ for (const node of [...nodes]) {
721
+ const { value: v, type } = node;
722
+ if (started.has(v)) continue;
723
+ started.add(v);
724
+ const previousScope = disposers;
725
+ disposers = node.scope;
726
+ try {
727
+ for (const [name, fn] of handlers.get(v) ?? []) {
728
+ const key = propertyName(name.slice(2));
729
+ if (!name.endsWith('Changed') && v[key]?.connect) v[key].connect(fn);
730
+ }
731
+ // Initial bindings can change a parent property before child built-in
732
+ // methods exist. Install change observers after this component's nodes
733
+ // have started, while retaining the pre-binding value for notification.
734
+ pendingPropertyHandlers.set(v, () => {
735
+ const previous = disposers;
736
+ disposers = node.scope;
737
+ try {
738
+ for (const [name, fn] of handlers.get(v) ?? [])
739
+ if (name.endsWith('Changed')) {
740
+ const prop = propertyName(name.slice(2, -7));
741
+ let prev = baselines.get(v)?.has(name)
742
+ ? baselines.get(v).get(name) : v[prop];
743
+ effect(() => {
744
+ const next = v[prop];
745
+ if (!Object.is(prev, next)) {
746
+ prev = next;
747
+ untrack(fn);
748
+ }
749
+ });
750
+ }
751
+ } finally { disposers = previous; }
752
+ });
753
+ const element = elements.get(v),
754
+ eventHandlers = handlers.get(v);
755
+ if (!nonvisual.has(type)) {
756
+ const dispatchKey = (target, eventType, event, seen = new Set()) => {
757
+ if (
758
+ seen.has(target) ||
759
+ !enabled(target) ||
760
+ target.Keys?.enabled === false
761
+ )
762
+ return false;
763
+ seen.add(target);
764
+ for (const next of target.Keys?.forwardTo ?? [])
765
+ if (next && dispatchKey(next, eventType, event, seen))
766
+ return true;
767
+ const callbacks = handlers.get(target);
768
+ const name =
769
+ event.key === 'ContextMenu'
770
+ ? 'Menu'
771
+ : event.key === 'AltGraph'
772
+ ? 'AltGr'
773
+ : event.key === ' '
774
+ ? 'Space'
775
+ : event.key === 'Enter'
776
+ ? event.code === 'NumpadEnter'
777
+ ? 'Enter'
778
+ : 'Return'
779
+ : event.key === 'Tab' && event.shiftKey
780
+ ? 'Backtab'
781
+ : event.key?.replace(/^Arrow/, '');
782
+ const key =
783
+ Qt['Key_' + name] ??
784
+ (event.key?.length === 1
785
+ ? event.key.toUpperCase().charCodeAt(0)
786
+ : 0);
787
+ const specific =
788
+ eventType === 'keydown' ? 'Keys.on' + name + 'Pressed' : null;
789
+ const data = {
790
+ key,
791
+ text: event.key?.length === 1 ? event.key : '',
792
+ modifiers:
793
+ (event.shiftKey ? Qt.ShiftModifier : 0) |
794
+ (event.ctrlKey ? Qt.ControlModifier : 0) |
795
+ (event.altKey ? Qt.AltModifier : 0) |
796
+ (event.metaKey ? Qt.MetaModifier : 0),
797
+ matches:standard=>{
798
+ const key=String(event.key).toLowerCase();
799
+ if(event.altKey)return false;
800
+ const command=event.ctrlKey||event.metaKey;
801
+ return standard===10 ? (command&&!event.shiftKey&&key==='v')||(event.shiftKey&&!command&&key==='insert')
802
+ : standard===9 ? command&&!event.shiftKey&&key==='c'
803
+ : standard===8 ? command&&!event.shiftKey&&key==='x'
804
+ : standard===26 ? command&&!event.shiftKey&&key==='a'
805
+ : standard===11 ? command&&!event.shiftKey&&key==='z'
806
+ : standard===12 ? command&&(key==='y'||event.shiftKey&&key==='z') : false;
807
+ },
808
+ isAutoRepeat: !!event.repeat,
809
+ accepted: !!callbacks?.has(specific),
810
+ };
811
+ if (specific && callbacks?.has(specific))
812
+ emit(target, specific, data);
813
+ if (!data.accepted)
814
+ emit(
815
+ target,
816
+ eventType === 'keydown' ? 'Keys.onPressed' : 'Keys.onReleased',
817
+ data,
818
+ );
819
+ return data.accepted;
820
+ };
821
+ for (const eventType of ['keydown', 'keyup'])
822
+ listen(element, eventType, (event) => {
823
+ if (event.defaultPrevented) return;
824
+ if (dispatchKey(v, eventType, event)) {
825
+ event.preventDefault();
826
+ event.stopPropagation();
827
+ }
828
+ });
829
+ if (['TextInput', 'TextField'].includes(type))
830
+ listen(element, 'keydown', (event) => {
831
+ if (
832
+ event.key === 'Enter' &&
833
+ !event.defaultPrevented &&
834
+ enabled(v)
835
+ ) {
836
+ emit(v, 'onAccepted');
837
+ emit(v, 'onEditingFinished');
838
+ event.preventDefault();
839
+ event.stopPropagation();
840
+ }
841
+ });
842
+ }
843
+ if (['TextInput', 'TextField', 'TextEdit'].includes(type))
844
+ listen(element, 'keydown', (event) => {
845
+ if (!enabled(v) || event.defaultPrevented) return;
846
+ const editing =
847
+ [
848
+ 'Backspace',
849
+ 'Delete',
850
+ 'ArrowLeft',
851
+ 'ArrowRight',
852
+ 'Home',
853
+ 'End',
854
+ ].includes(event.key) ||
855
+ (type === 'TextEdit' &&
856
+ ['ArrowUp', 'ArrowDown', 'Enter'].includes(event.key));
857
+ const typing =
858
+ !v.readOnly &&
859
+ event.key?.length === 1 &&
860
+ !event.ctrlKey &&
861
+ !event.metaKey &&
862
+ !event.altKey;
863
+ const shortcut =
864
+ (event.ctrlKey || event.metaKey) &&
865
+ ['a', 'c', 'v', 'x'].includes(event.key?.toLowerCase());
866
+ if (editing || typing || shortcut) event.stopPropagation();
867
+ });
868
+ if (eventHandlers?.has('onClicked') && type !== 'MouseArea')
869
+ listen(element, 'click', (event) => {
870
+ if (enabled(v)) emit(v, 'onClicked', pointer(v, element, event));
871
+ });
872
+ if (type === 'MouseArea') {
873
+ const areaTarget = v.propagateComposedEvents
874
+ ? (elements.get(parents.get(v)) ?? element)
875
+ : element;
876
+ const update = (event) => {
877
+ const data = pointer(v, element, event);
878
+ v.mouseX = data.x;
879
+ v.mouseY = data.y;
880
+ return data;
881
+ };
882
+ // A rejected Qt MouseArea press falls through to the next item in
883
+ // stacking order, not just to DOM ancestors. Retain that recipient
884
+ // for the release/click of this gesture (including nested overlays).
885
+ let passTarget = null, forwarding = false;
886
+ const forward = (event, target) => {
887
+ if (!target || areaTarget.contains(target) || forwarding) return;
888
+ forwarding = true;
889
+ try {
890
+ target.dispatchEvent(new MouseEvent(event.type, {
891
+ bubbles: true, cancelable: true, clientX: event.clientX, clientY: event.clientY,
892
+ button: event.button, buttons: event.buttons, detail: event.detail,
893
+ ctrlKey: event.ctrlKey, shiftKey: event.shiftKey, altKey: event.altKey, metaKey: event.metaKey,
894
+ }));
895
+ event.preventDefault();
896
+ event.stopPropagation();
897
+ } finally { forwarding = false; }
898
+ };
899
+ listen(areaTarget, 'mousedown', (event) => {
900
+ if (!enabled(v) || forwarding) return;
901
+ const data = update(event);
902
+ data.accepted = !!(v.acceptedButtons & button(event));
903
+ if (data.accepted) {
904
+ v.pressed = true;
905
+ emit(v, 'onPressed', data);
906
+ }
907
+ passTarget = null;
908
+ if (!data.accepted) {
909
+ v.pressed = false;
910
+ const old = areaTarget.style.pointerEvents;
911
+ areaTarget.style.pointerEvents = 'none';
912
+ try {
913
+ passTarget = document.elementFromPoint(event.clientX, event.clientY);
914
+ if (passTarget && areaTarget.contains(passTarget)) passTarget = null;
915
+ forward(event, passTarget);
916
+ }
917
+ finally { areaTarget.style.pointerEvents = old; }
918
+ }
919
+ });
920
+ for (const type of ['mouseup', 'click', 'auxclick', 'dblclick'])
921
+ listen(areaTarget, type, event => { if (passTarget) forward(event, passTarget); });
922
+ listen(document, 'mousemove', (event) => {
923
+ if (v.pressed || v.hoverEnabled)
924
+ emit(v, 'onPositionChanged', update(event));
925
+ });
926
+ listen(document, 'mouseup', (event) => {
927
+ if (!v.pressed) return;
928
+ v.pressed = false;
929
+ emit(v, 'onReleased', update(event));
930
+ });
931
+ for (const type of ['click', 'auxclick']) listen(areaTarget, type, (event) => {
932
+ if (!passTarget && enabled(v) && v.acceptedButtons & button(event))
933
+ emit(v, 'onClicked', update(event));
934
+ });
935
+ listen(areaTarget, 'dblclick', (event) => {
936
+ if (!passTarget && enabled(v)) emit(v, 'onDoubleClicked', update(event));
937
+ });
938
+ listen(areaTarget, 'mouseenter', (event) => {
939
+ v.containsMouse = true;
940
+ if (v.hoverEnabled) {
941
+ update(event);
942
+ emit(v, 'onEntered');
943
+ }
944
+ });
945
+ listen(areaTarget, 'mouseleave', () => {
946
+ v.containsMouse = false;
947
+ if (v.hoverEnabled) emit(v, 'onExited');
948
+ });
949
+ listen(areaTarget, 'wheel', (event) => {
950
+ if (!enabled(v) || !eventHandlers?.has('onWheel')) return;
951
+ const data = {
952
+ ...update(event),
953
+ angleDelta: { x: -event.deltaX, y: -event.deltaY },
954
+ pixelDelta: { x: 0, y: 0 },
955
+ phase: 0,
956
+ inverted: false,
957
+ accepted: true,
958
+ };
959
+ emit(v, 'onWheel', data);
960
+ if (data.accepted) event.preventDefault();
961
+ });
962
+ }
963
+ models.start(v, type);
964
+ media.start(v,type);
965
+ if (type === 'WorkerScript') {
966
+ let worker, current;
967
+ const open = () => {
968
+ const source = v.source;
969
+ if (source === current) return;
970
+ current = source;
971
+ worker?.();
972
+ worker = null;
973
+ v.ready = false;
974
+ if (!source) return;
975
+ if (!host?.worker)
976
+ throw Error('WorkerScript needs the local application host');
977
+ worker = host.worker(
978
+ new URL(source, descriptions.get(v).url).href,
979
+ (message) => emit(v, 'onMessage', message),
980
+ () => (v.ready = true),
981
+ (error) => {
982
+ v.ready = false;
983
+ console.error('QML WorkerScript: ' + error);
984
+ },
985
+ );
986
+ };
987
+ v.sendMessage = (message) => {
988
+ open();
989
+ if (!worker) throw Error('WorkerScript source is empty');
990
+ worker.send(message);
991
+ };
992
+ effect(open);
993
+ disposers?.push(() => worker?.());
994
+ }
995
+ if (type === 'IpcHandler') {
996
+ let target;
997
+ effect(() => {
998
+ if (target) ipcHandlers.delete(target);
999
+ target = v.enabled ? v.target : null;
1000
+ if (target) ipcHandlers.set(target, v);
1001
+ });
1002
+ disposers?.push(() => {
1003
+ if (target) ipcHandlers.delete(target);
1004
+ });
1005
+ }
1006
+ if (type === 'Binding') {
1007
+ let oldTarget,
1008
+ oldKey,
1009
+ oldValue,
1010
+ applied = false;
1011
+ const restore = () => {
1012
+ if (applied && oldTarget && v.restoreMode !== 0)
1013
+ oldTarget[oldKey] = oldValue;
1014
+ applied = false;
1015
+ };
1016
+ effect(() => {
1017
+ const target = v.target,
1018
+ key = v.property,
1019
+ when = v.when,
1020
+ next = v.value;
1021
+ untrack(() => {
1022
+ if (!when || target !== oldTarget || key !== oldKey) restore();
1023
+ if (!when || !target || !key) return;
1024
+ if (!applied) {
1025
+ oldTarget = target;
1026
+ oldKey = key;
1027
+ oldValue = target[key];
1028
+ applied = true;
1029
+ }
1030
+ target[key] = next;
1031
+ });
1032
+ });
1033
+ disposers?.push(restore);
1034
+ }
1035
+ if (type === 'PointHandler') {
1036
+ const target = elements.get(parents.get(v));
1037
+ const update = (event) => {
1038
+ const p = pointer(v, target, event);
1039
+ v.point = {
1040
+ position: { x: p.x, y: p.y },
1041
+ scenePosition: { x: event.clientX, y: event.clientY },
1042
+ };
1043
+ };
1044
+ listen(target, 'mousedown', (event) => {
1045
+ if (
1046
+ v.enabled &&
1047
+ enabled(parents.get(v)) &&
1048
+ v.acceptedButtons & button(event)
1049
+ ) {
1050
+ update(event);
1051
+ v.active = true;
1052
+ }
1053
+ });
1054
+ listen(document, 'mousemove', (event) => {
1055
+ if (v.active) update(event);
1056
+ });
1057
+ listen(document, 'mouseup', (event) => {
1058
+ if (v.active) {
1059
+ update(event);
1060
+ v.active = false;
1061
+ }
1062
+ });
1063
+ }
1064
+ if (type === 'DropArea') {
1065
+ const data = (event) => {
1066
+ const transfer = event.dataTransfer,
1067
+ rect = element.getBoundingClientRect(),
1068
+ types = [...(transfer?.types ?? [])];
1069
+ const get = (name) => transfer?.getData(name) || '';
1070
+ return {
1071
+ x: event.clientX - rect.x,
1072
+ y: event.clientY - rect.y,
1073
+ source: null,
1074
+ keys: types,
1075
+ formats: types,
1076
+ urls: get('text/uri-list')
1077
+ .split(/\r?\n/)
1078
+ .filter((line) => line && !line.startsWith('#')),
1079
+ text: get('text/plain'),
1080
+ hasText: types.includes('text/plain'),
1081
+ hasUrls: types.includes('text/uri-list'),
1082
+ proposedAction: 1,
1083
+ supportedActions: 7,
1084
+ action: 1,
1085
+ accepted: true,
1086
+ getDataAsString: get,
1087
+ acceptProposedAction() {
1088
+ this.accepted = true;
1089
+ this.action = this.proposedAction;
1090
+ },
1091
+ accept(action) {
1092
+ this.accepted = true;
1093
+ this.action = action;
1094
+ },
1095
+ };
1096
+ };
1097
+ for (const [eventType, handler] of [
1098
+ ['dragenter', 'onEntered'],
1099
+ ['dragover', 'onPositionChanged'],
1100
+ ['drop', 'onDropped'],
1101
+ ])
1102
+ listen(element, eventType, (event) => {
1103
+ if (!enabled(v)) return;
1104
+ const d = data(event);
1105
+ if (v.keys.length && !v.keys.some((key) => d.keys.includes(key)))
1106
+ return;
1107
+ v.drag = { x: d.x, y: d.y, source: d.source };
1108
+ emit(v, handler, d);
1109
+ v.containsDrag = eventType !== 'drop' && d.accepted;
1110
+ if (d.accepted) {
1111
+ event.preventDefault();
1112
+ event.stopPropagation();
1113
+ }
1114
+ });
1115
+ listen(element, 'dragleave', () => {
1116
+ v.containsDrag = false;
1117
+ emit(v, 'onExited');
1118
+ });
1119
+ }
1120
+ if (type === 'FrameAnimation') {
1121
+ let frame, last;
1122
+ const tick = (now) => {
1123
+ frame = undefined;
1124
+ if (!v.running || v.paused) return;
1125
+ v.frameTime = last === undefined ? 0 : (now - last) / 1000;
1126
+ last = now;
1127
+ v.smoothFrameTime = v.smoothFrameTime
1128
+ ? 0.9 * v.smoothFrameTime + 0.1 * v.frameTime
1129
+ : v.frameTime;
1130
+ v.elapsedTime += v.frameTime;
1131
+ v.currentFrame++;
1132
+ emit(v, 'onTriggered');
1133
+ frame = requestAnimationFrame(tick);
1134
+ };
1135
+ effect(() => {
1136
+ if (v.running && !v.paused) {
1137
+ if (frame === undefined) {
1138
+ last = undefined;
1139
+ frame = requestAnimationFrame(tick);
1140
+ }
1141
+ } else if (frame !== undefined) {
1142
+ cancelAnimationFrame(frame);
1143
+ frame = undefined;
1144
+ }
1145
+ });
1146
+ disposers?.push(() => {
1147
+ if (frame !== undefined) cancelAnimationFrame(frame);
1148
+ });
1149
+ }
1150
+ if (type === 'DragHandler') {
1151
+ const target = elements.get(parents.get(v));
1152
+ let origin = null,
1153
+ translation,
1154
+ startTarget;
1155
+ listen(target, 'mousedown', (event) => {
1156
+ if (
1157
+ !v.enabled ||
1158
+ !enabled(parents.get(v)) ||
1159
+ !(v.acceptedButtons & button(event))
1160
+ )
1161
+ return;
1162
+ origin = { x: event.clientX, y: event.clientY };
1163
+ translation = { ...v.persistentTranslation };
1164
+ startTarget = { x: v.target?.x || 0, y: v.target?.y || 0 };
1165
+ });
1166
+ listen(document, 'mousemove', (event) => {
1167
+ if (!origin) return;
1168
+ const x = v.xAxis.enabled ? event.clientX - origin.x : 0,
1169
+ y = v.yAxis.enabled ? event.clientY - origin.y : 0;
1170
+ if (!v.active && Math.hypot(x, y) < v.dragThreshold) return;
1171
+ v.centroid = {
1172
+ position: {
1173
+ x: pointer(v, target, event).x,
1174
+ y: pointer(v, target, event).y,
1175
+ },
1176
+ scenePosition: { x: event.clientX, y: event.clientY },
1177
+ };
1178
+ v.active = true;
1179
+ v.activeTranslation = { x, y };
1180
+ v.persistentTranslation = {
1181
+ x: translation.x + x,
1182
+ y: translation.y + y,
1183
+ };
1184
+ if (v.target) {
1185
+ if (v.xAxis.enabled)
1186
+ v.target.x = Math.max(
1187
+ v.xAxis.minimum,
1188
+ Math.min(v.xAxis.maximum, startTarget.x + x),
1189
+ );
1190
+ if (v.yAxis.enabled)
1191
+ v.target.y = Math.max(
1192
+ v.yAxis.minimum,
1193
+ Math.min(v.yAxis.maximum, startTarget.y + y),
1194
+ );
1195
+ }
1196
+ event.preventDefault();
1197
+ });
1198
+ listen(document, 'mouseup', () => {
1199
+ origin = null;
1200
+ v.activeTranslation = { x: 0, y: 0 };
1201
+ v.active = false;
1202
+ });
1203
+ }
1204
+ if (type === 'Image') images(v, element, descriptions.get(v).url);
1205
+ if (
1206
+ [
1207
+ 'NumberAnimation',
1208
+ 'PropertyAnimation',
1209
+ 'PauseAnimation',
1210
+ 'SequentialAnimation',
1211
+ 'ParallelAnimation',
1212
+ ].includes(type)
1213
+ ) {
1214
+ disposers?.push(() => animationStops.get(v)?.());
1215
+ effect(() => {
1216
+ if (v.running) {
1217
+ if (!animationStops.has(v))
1218
+ untrack(() => {
1219
+ emit(v, 'onStarted');
1220
+ animate(v);
1221
+ });
1222
+ } else {
1223
+ animationStops.get(v)?.();
1224
+ animationStops.delete(v);
1225
+ }
1226
+ });
1227
+ }
1228
+ if (type === 'Repeater' || type === 'Instantiator') {
1229
+ let instances = [];
1230
+ const clear = () => {
1231
+ for (const [index, instance] of instances.entries()) {
1232
+ if (type === 'Instantiator') emit(v, 'onObjectRemoved', index, instance.value);
1233
+ instance.dispose();
1234
+ }
1235
+ instances = [];
1236
+ };
1237
+ disposers?.push(clear);
1238
+ if (type === 'Instantiator') v.objectAt = index => instances[index]?.value ?? null;
1239
+ else v.itemAt = (index) => instances[index]?.value ?? null;
1240
+ effect(() => {
1241
+ if (type === 'Instantiator' && v.asynchronous) throw Error('Instantiator asynchronous creation is not implemented');
1242
+ const model = type === 'Instantiator' && !v.active ? [] : (v.model ?? []);
1243
+ if (
1244
+ typeof model === 'number' &&
1245
+ (!Number.isFinite(model) || model < 0 || model > 1000)
1246
+ )
1247
+ throw Error('Repeater count must be between 0 and 1000');
1248
+ const data =
1249
+ typeof model === 'number'
1250
+ ? Array.from(
1251
+ { length: Math.max(0, Math.floor(model)) },
1252
+ (_, i) => i,
1253
+ )
1254
+ : models.rows(model) ?? model;
1255
+ if (!Array.isArray(data) || data.length > 1000)
1256
+ throw Error('Repeater supports arrays or counts up to 1000');
1257
+ untrack(() => {
1258
+ clear();
1259
+ for (let index = 0; index < data.length; index++) {
1260
+ const previous = disposers,
1261
+ stops = [],
1262
+ before = nodes.length,
1263
+ pendingBefore = initial.length;
1264
+ const dispose = () => disposeScope(stops);
1265
+ disposers = stops;
1266
+ let value;
1267
+ try {
1268
+ value = factories.get(v)(index, data[index]);
1269
+ } catch (error) {
1270
+ initial.splice(pendingBefore);
1271
+ dispose();
1272
+ clear();
1273
+ v.count = 0;
1274
+ throw error;
1275
+ } finally {
1276
+ disposers = previous;
1277
+ }
1278
+ instances.push({
1279
+ value,
1280
+ dispose,
1281
+ });
1282
+ }
1283
+ v.count = instances.length;
1284
+ if (type === 'Instantiator') {
1285
+ v.object = instances[0]?.value ?? null;
1286
+ for (const [index, instance] of instances.entries()) emit(v, 'onObjectAdded', index, instance.value);
1287
+ }
1288
+ });
1289
+ });
1290
+ } else if (type === 'Loader') {
1291
+ let currentSource,
1292
+ currentComponent,
1293
+ currentVersion,
1294
+ currentActive,
1295
+ currentItem = null,
1296
+ loading = false,
1297
+ disposed = false,
1298
+ initialProperties = v._initialProperties ?? {};
1299
+ const clear = () => {
1300
+ currentItem?.destroy?.();
1301
+ v.item = null;
1302
+ v.status = 0;
1303
+ v.progress = 0;
1304
+ };
1305
+ disposers?.push(() => {
1306
+ disposed = true;
1307
+ clear();
1308
+ });
1309
+ let settingSource = false;
1310
+ const load = () => {
1311
+ if (settingSource || loading || disposed) return;
1312
+ const source = v.source,
1313
+ component = v.sourceComponent,
1314
+ active = v.active,
1315
+ version = v._reload;
1316
+ if (
1317
+ active === currentActive &&
1318
+ source === currentSource &&
1319
+ component === currentComponent &&
1320
+ version === currentVersion
1321
+ )
1322
+ return;
1323
+ loading = true;
1324
+ try {
1325
+ untrack(() => {
1326
+ clear();
1327
+ currentActive = active;
1328
+ currentSource = source;
1329
+ currentComponent = component;
1330
+ currentVersion = version;
1331
+ if (!active || (!source && !component)) return;
1332
+ v.status = 2;
1333
+ const factory =
1334
+ component ||
1335
+ qt(descriptions.get(v).url).createComponent(source);
1336
+ if (!factory || factory.status === 3) {
1337
+ v.status = 3;
1338
+ console.error(
1339
+ 'QML Loader: ' +
1340
+ (factory?.errorString?.() || 'Invalid component'),
1341
+ );
1342
+ return;
1343
+ }
1344
+ const props = { ...initialProperties };
1345
+ if (
1346
+ descriptions.get(v).properties.includes('width') ||
1347
+ v.anchors.fill ||
1348
+ (v.anchors.left && v.anchors.right)
1349
+ )
1350
+ props.width = Qt.binding(() => v.width);
1351
+ if (
1352
+ descriptions.get(v).properties.includes('height') ||
1353
+ v.anchors.fill ||
1354
+ (v.anchors.top && v.anchors.bottom)
1355
+ )
1356
+ props.height = Qt.binding(() => v.height);
1357
+ v.item = factory.createObject(v, props);
1358
+ v.status = v.item ? 1 : 3;
1359
+ v.progress = v.item ? 1 : 0;
1360
+ if (v.item) emit(v, 'onLoaded');
1361
+ });
1362
+ } finally {
1363
+ loading = false;
1364
+ }
1365
+ };
1366
+ // A synchronous Loader must materialize a dirtied active binding before
1367
+ // the very next statement reads item, without flushing unrelated effects.
1368
+ Object.defineProperty(v, 'item', {
1369
+ configurable: true,
1370
+ enumerable: true,
1371
+ get() {
1372
+ if (!loading && !disposed) untrack(load);
1373
+ return currentItem;
1374
+ },
1375
+ set(next) {
1376
+ currentItem = next;
1377
+ },
1378
+ });
1379
+ v.setSource = (source, properties = {}) => {
1380
+ initialProperties = properties;
1381
+ settingSource = true;
1382
+ try {
1383
+ v.source = String(source);
1384
+ v._reload++;
1385
+ } finally {
1386
+ settingSource = false;
1387
+ }
1388
+ load();
1389
+ };
1390
+ for (const key of ['active', 'source', 'sourceComponent'])
1391
+ watchWrite(v, key, () => untrack(load));
1392
+ effect(load);
1393
+ effect(() => {
1394
+ v.implicitWidth = v.item?.implicitWidth ?? v.item?.width ?? 0;
1395
+ v.implicitHeight = v.item?.implicitHeight ?? v.item?.height ?? 0;
1396
+ });
1397
+ } else if (type === 'TapHandler') {
1398
+ const target = elements.get(parents.get(v));
1399
+ if (!target) throw Error('TapHandler needs a visual parent');
1400
+ const update = (event) => {
1401
+ const data = pointer(v, target, event);
1402
+ v.point = {
1403
+ position: { x: data.x, y: data.y },
1404
+ scenePosition: { x: event.clientX, y: event.clientY },
1405
+ pressedButtons: event.buttons ?? 0,
1406
+ modifiers: data.modifiers,
1407
+ };
1408
+ return v.point;
1409
+ };
1410
+ listen(target, 'mousedown', (event) => {
1411
+ if (
1412
+ v.enabled &&
1413
+ enabled(parents.get(v)) &&
1414
+ v.acceptedButtons & button(event)
1415
+ ) {
1416
+ update(event);
1417
+ v.pressed = true;
1418
+ }
1419
+ });
1420
+ listen(document, 'mousemove', (event) => {
1421
+ if (v.pressed) update(event);
1422
+ });
1423
+ listen(document, 'mouseup', (event) => {
1424
+ if (v.pressed) {
1425
+ update(event);
1426
+ v.pressed = false;
1427
+ }
1428
+ });
1429
+ for (const type of ['click', 'auxclick']) listen(target, type, (event) => {
1430
+ if (
1431
+ !v.enabled ||
1432
+ !enabled(parents.get(v)) ||
1433
+ !(v.acceptedButtons & button(event))
1434
+ )
1435
+ return;
1436
+ const point = update(event);
1437
+ if (
1438
+ parents.get(v).containmentMask &&
1439
+ !parents.get(v).contains(point.position)
1440
+ )
1441
+ return;
1442
+ v.tapCount = event.detail || 1;
1443
+ emit(v, 'onTapped', point, button(event));
1444
+ if ((event.detail ?? 1) <= 1)
1445
+ emit(v, 'onSingleTapped', point, button(event));
1446
+ });
1447
+ listen(target, 'dblclick', (event) => {
1448
+ if (
1449
+ v.enabled &&
1450
+ enabled(parents.get(v)) &&
1451
+ v.acceptedButtons & button(event)
1452
+ ) {
1453
+ const point = update(event);
1454
+ if (
1455
+ parents.get(v).containmentMask &&
1456
+ !parents.get(v).contains(point.position)
1457
+ )
1458
+ return;
1459
+ if (v.tapCount !== 2) {
1460
+ v.tapCount = 2;
1461
+ emit(v, 'onTapped', point, button(event));
1462
+ }
1463
+ emit(v, 'onDoubleTapped', point, button(event));
1464
+ }
1465
+ });
1466
+ } else if (type === 'HoverHandler') {
1467
+ const target = elements.get(parents.get(v));
1468
+ if (!target) throw Error('HoverHandler needs a visual parent');
1469
+ effect(() => {
1470
+ target.style.cursor =
1471
+ v.enabled && v.cursorShape === 13 ? 'pointer' : 'auto';
1472
+ });
1473
+ const updateHover = (event) => {
1474
+ const parent = parents.get(v);
1475
+ if (!v.enabled || !enabled(parent)) {
1476
+ v.hovered = false;
1477
+ return;
1478
+ }
1479
+ const data = pointer(v, target, event);
1480
+ let inside = parent.contains(data);
1481
+ for (let p = parents.get(parent); inside && p; p = parents.get(p))
1482
+ if (p.clip) {
1483
+ const rect = elements.get(p).getBoundingClientRect();
1484
+ inside =
1485
+ event.clientX >= rect.left &&
1486
+ event.clientX < rect.right &&
1487
+ event.clientY >= rect.top &&
1488
+ event.clientY < rect.bottom;
1489
+ }
1490
+ v.hovered = inside;
1491
+ if (inside)
1492
+ v.point = {
1493
+ position: { x: data.x, y: data.y },
1494
+ scenePosition: { x: event.clientX, y: event.clientY },
1495
+ };
1496
+ };
1497
+ listen(document, 'mousemove', updateHover);
1498
+ listen(document, 'mouseout', updateHover);
1499
+ effect(() => {
1500
+ if (!v.enabled || !enabled(parents.get(v))) v.hovered = false;
1501
+ });
1502
+ listen(document, 'mouseleave', () => {
1503
+ v.hovered = false;
1504
+ });
1505
+ } else if (type === 'FileView') {
1506
+ let lastPath, lastWatch, unwatch;
1507
+ disposers?.push(() => unwatch?.());
1508
+ effect(() => {
1509
+ const path = v.path,
1510
+ watch = v.watchChanges;
1511
+ if (path === lastPath && watch === lastWatch) return;
1512
+ lastPath = path;
1513
+ lastWatch = watch;
1514
+ unwatch?.();
1515
+ if (path) {
1516
+ if (v.preload) untrack(() => v.reload());
1517
+ if (watch)
1518
+ unwatch = host.watch(path, () => emit(v, 'onFileChanged'));
1519
+ }
1520
+ });
1521
+ } else if (type === 'Socket') {
1522
+ const status = socketStates.get(v);
1523
+ let connection = null, generation = 0, pending = '';
1524
+ const stop = () => { generation++; connection?.(); connection = null; pending = ''; status.actual = false; };
1525
+ disposers?.push(stop);
1526
+ v.write = text => { if (status.actual) connection?.write(text); };
1527
+ v.flush = () => connection?.flush();
1528
+ effect(() => {
1529
+ const wanted = status.wanted, path = v.path;
1530
+ if (!wanted) { untrack(stop); return; }
1531
+ if (!path || connection) return;
1532
+ untrack(() => {
1533
+ const current = ++generation;
1534
+ connection = host.socket(path, {
1535
+ open() { if (current === generation) status.actual = true; },
1536
+ data(chunk) {
1537
+ if (current !== generation) return;
1538
+ pending += chunk;
1539
+ const parser = v.parser;
1540
+ if (!parser) { pending = ''; return; }
1541
+ const marker = String(parser.splitMarker ?? '\n');
1542
+ if (!marker) { const text = pending; pending = ''; emit(parser, 'onRead', text); return; }
1543
+ let at;
1544
+ while ((at = pending.indexOf(marker)) >= 0) {
1545
+ const text = pending.slice(0, at); pending = pending.slice(at + marker.length);
1546
+ emit(parser, 'onRead', text);
1547
+ if (current !== generation) return;
1548
+ }
1549
+ },
1550
+ error(error) { if (current === generation) emit(v, 'onError', error); },
1551
+ close() {
1552
+ if (current !== generation) return;
1553
+ connection = null; pending = ''; status.actual = false; status.wanted = false;
1554
+ },
1555
+ });
1556
+ });
1557
+ });
1558
+ } else if (type === 'Process') {
1559
+ let child = null,
1560
+ generation = 0;
1561
+ disposers?.push(() => {
1562
+ generation++;
1563
+ child?.();
1564
+ });
1565
+ v.write = (text) => {
1566
+ if (!child?.write)
1567
+ throw Error('Process is not running or stdin is disabled');
1568
+ child.write(text);
1569
+ };
1570
+ v.signal = (signal) => child?.signal?.(signal);
1571
+ v.closeWriteChannel = () => child?.closeWriteChannel?.();
1572
+ const parser = (value) => {
1573
+ let pending = '', byteCount = 0, cachedData = new ArrayBuffer(0);
1574
+ const chunks = [], dataRevision = state({ value: 0 });
1575
+ let cachedRevision = 0;
1576
+ const collector = value && owners.get(value)?.type === 'StdioCollector';
1577
+ if (collector) {
1578
+ value.text = '';
1579
+ Object.defineProperty(value, 'data', { configurable: true, enumerable: true, get() {
1580
+ const revision = dataRevision.value;
1581
+ if (revision !== cachedRevision) {
1582
+ const data = new Uint8Array(byteCount);
1583
+ let at = 0;
1584
+ for (const bytes of chunks) { data.set(bytes, at); at += bytes.length; }
1585
+ cachedData = data.buffer; cachedRevision = revision;
1586
+ }
1587
+ return cachedData;
1588
+ } });
1589
+ }
1590
+ return (chunk, final = false, bytes = null) => {
1591
+ if (!value) return;
1592
+ if (collector) {
1593
+ if (bytes?.length) { chunks.push(new Uint8Array(bytes)); byteCount += bytes.length; }
1594
+ if (final || !value.waitForEnd) dataRevision.value++;
1595
+ }
1596
+ if (owners.get(value)?.type === 'SplitParser') {
1597
+ pending += chunk;
1598
+ const marker = value.splitMarker;
1599
+ if (!marker) {
1600
+ if (pending) {
1601
+ emit(value, 'onRead', pending);
1602
+ pending = '';
1603
+ }
1604
+ return;
1605
+ }
1606
+ let at;
1607
+ while ((at = pending.indexOf(marker)) >= 0) {
1608
+ emit(value, 'onRead', pending.slice(0, at));
1609
+ pending = pending.slice(at + marker.length);
1610
+ }
1611
+ if (final && pending) {
1612
+ emit(value, 'onRead', pending);
1613
+ pending = '';
1614
+ }
1615
+ } else if (final) {
1616
+ value.text = pending + chunk;
1617
+ emit(value, 'onStreamFinished');
1618
+ } else {
1619
+ pending += chunk;
1620
+ if (!value.waitForEnd) value.text = pending;
1621
+ }
1622
+ };
1623
+ };
1624
+ effect(() => {
1625
+ const running = v.running;
1626
+ if (running && !child)
1627
+ untrack(() => {
1628
+ const thisRun = ++generation,
1629
+ out = parser(v.stdout),
1630
+ err = parser(v.stderr);
1631
+ child = host.run(
1632
+ v.command,
1633
+ (result) => {
1634
+ if (thisRun !== generation) return;
1635
+ child = null;
1636
+ v.processId = 0;
1637
+ out('', true);
1638
+ err('', true);
1639
+ v.running = false;
1640
+ if (!result.error) emit(v, 'onExited', result.exitCode, 0);
1641
+ },
1642
+ {
1643
+ stdin: v.stdinEnabled,
1644
+ cwd: v.workingDirectory,
1645
+ environment: v.environment,
1646
+ clearEnvironment: v.clearEnvironment,
1647
+ stdout: (chunk, bytes) => {
1648
+ if (thisRun === generation) out(chunk, false, bytes);
1649
+ },
1650
+ stderr: (chunk, bytes) => {
1651
+ if (thisRun === generation) err(chunk, false, bytes);
1652
+ },
1653
+ },
1654
+ );
1655
+ v.processId = child.processId ?? 0;
1656
+ if (v.processId) emit(v, 'onStarted');
1657
+ });
1658
+ else if (!running && child) {
1659
+ generation++;
1660
+ child();
1661
+ child = null;
1662
+ v.processId = 0;
1663
+ }
1664
+ });
1665
+ } else if (type === 'Timer') {
1666
+ let timer;
1667
+ disposers?.push(() => clearTimeout(timer));
1668
+ const schedule = () => {
1669
+ clearTimeout(timer);
1670
+ if (v.running)
1671
+ timer = setTimeout(
1672
+ () => {
1673
+ if (!v.repeat) v.running = false;
1674
+ emit(v, 'onTriggered');
1675
+ schedule();
1676
+ },
1677
+ Math.max(1, v.interval),
1678
+ );
1679
+ };
1680
+ effect(() => {
1681
+ v.running;
1682
+ v.interval;
1683
+ schedule();
1684
+ });
1685
+ v.restart = () => {
1686
+ v.running = true;
1687
+ schedule();
1688
+ };
1689
+ v.start = () => {
1690
+ v.running = true;
1691
+ };
1692
+ v.stop = () => {
1693
+ v.running = false;
1694
+ };
1695
+ } else if (type === 'Connections') {
1696
+ let links = [];
1697
+ disposers?.push(() => {
1698
+ for (const [signal, fn] of links) signal.disconnect(fn);
1699
+ });
1700
+ const wiring = effect(() => {
1701
+ for (const [signal, fn] of links) signal.disconnect(fn);
1702
+ links = [];
1703
+ const target = v.target;
1704
+ if (!target || v.enabled === false) return;
1705
+ for (const [name, fn] of handlers.get(v) ?? []) {
1706
+ const key = propertyName(name.slice(2));
1707
+ if (target[key]?.connect) {
1708
+ target[key].connect(fn);
1709
+ links.push([target[key], fn]);
1710
+ } else if (
1711
+ key.endsWith('Changed') &&
1712
+ key.slice(0, -7) in target
1713
+ ) {
1714
+ const property = key.slice(0, -7);
1715
+ let previous = untrack(() => target[property]);
1716
+ const stop = effect(() => {
1717
+ const next = target[property];
1718
+ if (!Object.is(next, previous)) {
1719
+ previous = next;
1720
+ untrack(fn);
1721
+ }
1722
+ });
1723
+ links.push([{ disconnect: stop }, fn]);
1724
+ } else if (!v.ignoreUnknownSignals)
1725
+ throw Error('Unknown QML signal: ' + key);
1726
+ }
1727
+ });
1728
+ wiring.onInvalidate = () => pendingConnections.add(wiring);
1729
+ disposers?.push(() => pendingConnections.delete(wiring));
1730
+ }
1731
+ } finally {
1732
+ disposers = previousScope;
1733
+ }
1734
+ }
1735
+ for (const [value, install] of [...pendingPropertyHandlers]) {
1736
+ pendingPropertyHandlers.delete(value);
1737
+ if (!destroyedNodes.has(value)) install();
1738
+ }
1739
+ reactive.flush();
1740
+ };
1741
+ const cssColor = (v) =>
1742
+ v && typeof v === 'object' && 'r' in v
1743
+ ? `rgba(${Math.round(v.r * 255)},${Math.round(v.g * 255)},${Math.round(v.b * 255)},${v.a})`
1744
+ : v;
1745
+ const viewport = state({ width: innerWidth, height: innerHeight });
1746
+ addEventListener('resize', () => {
1747
+ viewport.width = innerWidth;
1748
+ viewport.height = innerHeight;
1749
+ });
1750
+ const make = (element, type, parent = null) => {
1751
+ const objectLimit = host ? 30000 : 2000;
1752
+ if (nodes.length >= objectLimit)
1753
+ throw Object.assign(
1754
+ Error('QML live object limit (' + objectLimit + ')'),
1755
+ { qmlFatal: true },
1756
+ );
1757
+ const value = state({
1758
+ width: null,
1759
+ height: null,
1760
+ implicitWidth: null,
1761
+ implicitHeight: null,
1762
+ x: 0,
1763
+ y: 0,
1764
+ color: [
1765
+ 'Text',
1766
+ 'Label',
1767
+ 'TextField',
1768
+ 'TextInput',
1769
+ 'TextEdit',
1770
+ 'Button',
1771
+ ].includes(type)
1772
+ ? 'black'
1773
+ : 'transparent',
1774
+ smooth:true,
1775
+ antialiasing:true, // Existing CSS/CanvasKit geometry is antialiased.
1776
+ radius: 0,
1777
+ opacity: 1,
1778
+ visible: true,
1779
+ enabled: true,
1780
+ clip: false,
1781
+ text: '',
1782
+ title: '',
1783
+ font: state({
1784
+ pixelSize: 16,
1785
+ family: 'sans-serif',
1786
+ bold: false,
1787
+ italic: false,
1788
+ letterSpacing: 0,
1789
+ capitalization: 0,
1790
+ features: {},
1791
+ }),
1792
+ border: state({ width: 0, color: 'black' }),
1793
+ anchors: state({
1794
+ fill: null,
1795
+ centerIn: null,
1796
+ horizontalCenter: null,
1797
+ verticalCenter: null,
1798
+ left: null,
1799
+ right: null,
1800
+ top: null,
1801
+ bottom: null,
1802
+ baseline: null,
1803
+ baselineOffset: 0,
1804
+ margins: 0,
1805
+ leftMargin: null,
1806
+ rightMargin: null,
1807
+ topMargin: null,
1808
+ bottomMargin: null,
1809
+ horizontalCenterOffset: 0,
1810
+ verticalCenterOffset: 0,
1811
+ }),
1812
+ scale: 1,
1813
+ rotation: 0,
1814
+ z: 0,
1815
+ transform: null,
1816
+ containmentMask: null,
1817
+ objectName: '',
1818
+ activeFocus: false,
1819
+ focus: false,
1820
+ activeFocusOnTab: false,
1821
+ topPadding: 0,
1822
+ bottomPadding: 0,
1823
+ leftPadding: 0,
1824
+ rightPadding: 0,
1825
+ maximumLineCount: Infinity,
1826
+ lineCount: 1,
1827
+ renderType: 0,
1828
+ verticalAlignment: 32,
1829
+ wrapMode: 0,
1830
+ elide: 0,
1831
+ lineHeight: 1,
1832
+ lineHeightMode: 0,
1833
+ baselineOffset: 0,
1834
+ linkColor: '#0000ff',
1835
+ hoveredLink: '',
1836
+ truncated: false,
1837
+ contentWidth: 0,
1838
+ contentHeight: 0,
1839
+ horizontalAlignment: 0,
1840
+ textFormat: ['Text', 'Label'].includes(type) ? 2 : 0,
1841
+ spacing: 0,
1842
+ placeholderText: '',
1843
+ readOnly: false,
1844
+ selectByMouse: false,
1845
+ Drag: state({
1846
+ active: false,
1847
+ source: null,
1848
+ keys: [],
1849
+ mimeData: {},
1850
+ supportedActions: 1,
1851
+ proposedAction: 1,
1852
+ dragType: 2,
1853
+ imageSource: '',
1854
+ hotSpot: state({ x: 0, y: 0 }),
1855
+ }),
1856
+ Accessible: state({ name: '', role: 0, checked: false }),
1857
+ Layout: state({
1858
+ fillWidth: false,
1859
+ fillHeight: false,
1860
+ minimumWidth: 0,
1861
+ minimumHeight: 0,
1862
+ maximumWidth: Infinity,
1863
+ maximumHeight: Infinity,
1864
+ preferredWidth: -1,
1865
+ preferredHeight: -1,
1866
+ alignment: 0,
1867
+ margins: 0,
1868
+ leftMargin: null,
1869
+ rightMargin: null,
1870
+ topMargin: null,
1871
+ bottomMargin: null,
1872
+ }),
1873
+ Keys: state({ enabled: true, forwardTo: [] }),
1874
+ });
1875
+ const node = { value, type, scope: disposers };
1876
+ nodes.push(node);
1877
+ owners.set(value, node);
1878
+ for (const key of [
1879
+ 'font',
1880
+ 'border',
1881
+ 'anchors',
1882
+ 'Accessible',
1883
+ 'Keys',
1884
+ 'Layout',
1885
+ ])
1886
+ owners.set(value[key], node);
1887
+ elements.set(value, element);
1888
+ if (parent?.contentItem && !nonvisual.has(type))
1889
+ parent = parent.contentItem;
1890
+ parents.set(value, parent);
1891
+ moveChild(value, null, parent);
1892
+ if (parent && element.parentElement !== elements.get(parent))
1893
+ elements.get(parent)?.appendChild(element);
1894
+ Object.defineProperty(value, 'parent', {
1895
+ configurable: true,
1896
+ get: () => parents.get(value),
1897
+ set: (next) => {
1898
+ for (let p = next; p; p = parents.get(p))
1899
+ if (p === value) throw Error('Cyclic Item parent');
1900
+ moveChild(value, parents.get(value), next);
1901
+ parents.set(value, next);
1902
+ (elements.get(next) ?? document.body).appendChild(element);
1903
+ untrack(() => structure.version++);
1904
+ },
1905
+ });
1906
+ if (!nonvisual.has(type)) {
1907
+ for (const edge of [
1908
+ 'left',
1909
+ 'right',
1910
+ 'top',
1911
+ 'bottom',
1912
+ 'horizontalCenter',
1913
+ 'verticalCenter',
1914
+ 'baseline',
1915
+ ]) {
1916
+ const line = { item: value, edge };
1917
+ Object.defineProperty(value, edge, { get: () => line });
1918
+ }
1919
+ for (const key of ['children', 'data'])
1920
+ Object.defineProperty(value, key, { get: () => childrenOf(value) });
1921
+ Object.defineProperty(value, 'childrenRect', {
1922
+ get: () => {
1923
+ const children = childrenOf(value);
1924
+ if (!children.length) return { x: 0, y: 0, width: 0, height: 0 };
1925
+ const x = Math.min(...children.map((v) => v.x)),
1926
+ y = Math.min(...children.map((v) => v.y));
1927
+ return {
1928
+ x,
1929
+ y,
1930
+ width: Math.max(...children.map((v) => v.x + (v.width || 0))) - x,
1931
+ height: Math.max(...children.map((v) => v.y + (v.height || 0))) - y,
1932
+ };
1933
+ },
1934
+ });
1935
+ }
1936
+ value.forceActiveFocus = () => {
1937
+ if (enabled(value))
1938
+ untrack(() => elements.get(focusTarget(value)).focus());
1939
+ };
1940
+ value.contains = (point) =>
1941
+ point.x >= 0 &&
1942
+ point.y >= 0 &&
1943
+ point.x < value.width &&
1944
+ point.y < value.height &&
1945
+ (!value.containmentMask || value.containmentMask.contains(point));
1946
+ value.mapToItem = (item, x, y) => {
1947
+ const a = element.getBoundingClientRect(),
1948
+ b = elements.get(item)?.getBoundingClientRect() ?? { x: 0, y: 0 };
1949
+ return { x: a.x - b.x + x, y: a.y - b.y + y };
1950
+ };
1951
+ value.mapFromItem = (item, x, y) => {
1952
+ const a = element.getBoundingClientRect(),
1953
+ b = elements.get(item)?.getBoundingClientRect() ?? { x: 0, y: 0 };
1954
+ return { x: b.x - a.x + x, y: b.y - a.y + y };
1955
+ };
1956
+ value.Window=state({window:null});
1957
+ effect(()=>{
1958
+ structure.version;
1959
+ let windowValue=type==='FloatingWindow'?value:parents.get(value);
1960
+ while(windowValue&&owners.get(windowValue)?.type!=='FloatingWindow')windowValue=parents.get(windowValue);
1961
+ value.Window.window=windowValue??null;
1962
+ });
1963
+ let viewValue = parent;
1964
+ while (
1965
+ viewValue &&
1966
+ !['ListView', 'GridView'].includes(owners.get(viewValue)?.type)
1967
+ )
1968
+ viewValue = parents.get(viewValue);
1969
+ value.ListView = {
1970
+ view: owners.get(viewValue)?.type === 'ListView' ? viewValue : null,
1971
+ };
1972
+ value.GridView = {
1973
+ view: owners.get(viewValue)?.type === 'GridView' ? viewValue : null,
1974
+ };
1975
+ if (type === 'FloatingWindow') {
1976
+ value.minimumSize=state({width:0,height:0});value.maximumSize=state({width:0,height:0});
1977
+ value.sceneGraphInvalidated=signal();
1978
+ disposers?.push(()=>value.sceneGraphInvalidated());
1979
+ effect(()=>{element.setAttribute('data-qml-window-min',JSON.stringify([value.minimumSize.width,value.minimumSize.height]));element.setAttribute('data-qml-window-max',JSON.stringify([value.maximumSize.width,value.maximumSize.height]));});
1980
+ value.sceneGraphError = signal();
1981
+ value.afterAnimating = signal();
1982
+ value.afterSynchronizing = signal();
1983
+ value.frameSwapped = signal();
1984
+ value.activeFocusItem = null;
1985
+ let frame,
1986
+ hadFrame = false,
1987
+ updateRequested = false,
1988
+ disposed = false;
1989
+ const schedule = () => {
1990
+ if (!disposed && frame === undefined) frame = requestAnimationFrame(tick);
1991
+ };
1992
+ value.update = () => {
1993
+ if (disposed) return;
1994
+ updateRequested = true;
1995
+ schedule();
1996
+ };
1997
+ const tick = () => {
1998
+ frame = undefined;
1999
+ if (disposed) return;
2000
+ if (
2001
+ !updateRequested &&
2002
+ !value.afterAnimating.listenerCount &&
2003
+ !value.afterSynchronizing.listenerCount &&
2004
+ !value.frameSwapped.listenerCount
2005
+ )
2006
+ return;
2007
+ updateRequested = false;
2008
+ if (hadFrame) value.frameSwapped();
2009
+ value.afterAnimating();
2010
+ textures.flush();
2011
+ value.afterSynchronizing();
2012
+ hadFrame = true;
2013
+ schedule();
2014
+ };
2015
+ for (const event of [value.afterAnimating, value.afterSynchronizing, value.frameSwapped]) {
2016
+ const connect = event.connect;
2017
+ event.connect = (fn) => {
2018
+ connect(fn);
2019
+ schedule();
2020
+ };
2021
+ }
2022
+ disposers?.push(() => {
2023
+ disposed = true;
2024
+ if (frame !== undefined) cancelAnimationFrame(frame);
2025
+ });
2026
+
2027
+ value.itemRect = (item) => {
2028
+ const a = elements.get(item)?.getBoundingClientRect(),
2029
+ b = element.getBoundingClientRect();
2030
+ return a
2031
+ ? { x: a.x - b.x, y: a.y - b.y, width: a.width, height: a.height }
2032
+ : { x: 0, y: 0, width: 0, height: 0 };
2033
+ };
2034
+ }
2035
+ untrack(() => structure.version++);
2036
+ if (type === 'Behavior')
2037
+ Object.assign(value, {
2038
+ enabled: true,
2039
+ targetProperty: null,
2040
+ animation: null,
2041
+ });
2042
+ if (
2043
+ [
2044
+ 'NumberAnimation',
2045
+ 'PropertyAnimation',
2046
+ 'PauseAnimation',
2047
+ 'SequentialAnimation',
2048
+ 'ParallelAnimation',
2049
+ 'ScriptAction',
2050
+ 'PropertyAction',
2051
+ ].includes(type)
2052
+ )
2053
+ Object.assign(value, {
2054
+ target: null,
2055
+ targets: [],
2056
+ property: '',
2057
+ properties: '',
2058
+ from: undefined,
2059
+ to: undefined,
2060
+ duration: 250,
2061
+ easing: state({ type: 0 }),
2062
+ running: false,
2063
+ loops: 1,
2064
+ start: () => {
2065
+ value.running = true;
2066
+ },
2067
+ stop: () => {
2068
+ animationStops.get(value)?.();
2069
+ animationStops.delete(value);
2070
+ value.running = false;
2071
+ },
2072
+ restart: () => {
2073
+ value.stop();
2074
+ value.running = true;
2075
+ },
2076
+ complete: () => {
2077
+ if (value.target && value.property && value.to !== undefined)
2078
+ value.target[value.property] = value.to;
2079
+ value.stop();
2080
+ },
2081
+ });
2082
+ if (['Flickable', 'ListView', 'GridView'].includes(type)) {
2083
+ Object.assign(value, {
2084
+ contentX: 0,
2085
+ contentY: 0,
2086
+ contentWidth: 0,
2087
+ contentHeight: 0,
2088
+ contentItem: null,
2089
+ interactive: true,
2090
+ boundsBehavior: 0,
2091
+ boundsMovement: 0,
2092
+ flickableDirection: 0,
2093
+ moving: false,
2094
+ flicking: false,
2095
+ dragging: false,
2096
+ originX: 0,
2097
+ originY: 0,
2098
+ leftMargin: 0,
2099
+ rightMargin: 0,
2100
+ topMargin: 0,
2101
+ bottomMargin: 0,
2102
+ });
2103
+ const content = document.createElement('div');
2104
+ content.setAttribute('data-qml-content', '');
2105
+ for (const child of [...element.children]) content.appendChild(child);
2106
+ element.appendChild(content);
2107
+ value.contentItem = make(content, 'Item', value);
2108
+ effect(() => {
2109
+ value.contentItem.width = value.contentWidth;
2110
+ value.contentItem.height = value.contentHeight;
2111
+ value.contentItem.x = -value.contentX;
2112
+ value.contentItem.y = -value.contentY;
2113
+ });
2114
+ for (const [key, read] of Object.entries({
2115
+ atXBeginning: () => value.contentX <= -value.leftMargin,
2116
+ atXEnd: () =>
2117
+ value.contentX >=
2118
+ value.contentWidth - value.width + value.rightMargin,
2119
+ atYBeginning: () => value.contentY <= -value.topMargin,
2120
+ atYEnd: () =>
2121
+ value.contentY >=
2122
+ value.contentHeight - value.height + value.bottomMargin,
2123
+ }))
2124
+ Object.defineProperty(value, key, { get: read });
2125
+ value.movementStarted = signal();
2126
+ value.movementEnded = signal();
2127
+ value.flickStarted = signal();
2128
+ value.flickEnded = signal();
2129
+ value.cancelFlick = () => {
2130
+ if (value.flicking) value.flickEnded();
2131
+ value.flicking = false;
2132
+ if (value.moving) value.movementEnded();
2133
+ value.moving = false;
2134
+ };
2135
+ value.returnToBounds = () => {
2136
+ value.contentX = Math.max(
2137
+ -value.leftMargin,
2138
+ Math.min(
2139
+ Math.max(
2140
+ -value.leftMargin,
2141
+ value.contentWidth - value.width + value.rightMargin,
2142
+ ),
2143
+ value.contentX,
2144
+ ),
2145
+ );
2146
+ value.contentY = Math.max(
2147
+ -value.topMargin,
2148
+ Math.min(
2149
+ Math.max(
2150
+ -value.topMargin,
2151
+ value.contentHeight - value.height + value.bottomMargin,
2152
+ ),
2153
+ value.contentY,
2154
+ ),
2155
+ );
2156
+ };
2157
+ listen(element, 'wheel', (event) => {
2158
+ if (!enabled(value) || !value.interactive || event.defaultPrevented)
2159
+ return;
2160
+ if (value.flickableDirection !== 2) value.contentX += event.deltaX;
2161
+ if (value.flickableDirection !== 1) value.contentY += event.deltaY;
2162
+ value.returnToBounds();
2163
+ event.preventDefault();
2164
+ });
2165
+ }
2166
+ if (type === 'IpcHandler')
2167
+ Object.assign(value, { target: '', enabled: true });
2168
+ if (type === 'Binding')
2169
+ Object.assign(value, {
2170
+ target: null,
2171
+ property: '',
2172
+ value: undefined,
2173
+ when: true,
2174
+ delayed: false,
2175
+ restoreMode: 3,
2176
+ });
2177
+ if (type === 'Gradient')
2178
+ Object.assign(value, { orientation: 0, stops: [] });
2179
+ if (type === 'GradientStop')
2180
+ Object.assign(value, { position: 0, color: 'black' });
2181
+ if (type === 'WorkerScript')
2182
+ Object.assign(value, { source: '', ready: false });
2183
+ if (type === 'LoggingCategory')
2184
+ Object.assign(value, { name: '', defaultLogLevel: 0 });
2185
+ if (type === 'PointHandler')
2186
+ Object.assign(value, {
2187
+ target: parent,
2188
+ acceptedButtons: 1,
2189
+ active: false,
2190
+ point: { position: { x: 0, y: 0 }, scenePosition: { x: 0, y: 0 } },
2191
+ });
2192
+ if (type === 'DropArea')
2193
+ Object.assign(value, {
2194
+ keys: [],
2195
+ containsDrag: false,
2196
+ drag: { x: 0, y: 0, source: null },
2197
+ });
2198
+ if (type === 'DragHandler')
2199
+ Object.assign(value, {
2200
+ target: parent,
2201
+ acceptedButtons: 1,
2202
+ active: false,
2203
+ dragThreshold: 10,
2204
+ persistentTranslation: { x: 0, y: 0 },
2205
+ activeTranslation: { x: 0, y: 0 },
2206
+ centroid: { position: { x: 0, y: 0 }, scenePosition: { x: 0, y: 0 } },
2207
+ xAxis: state({ enabled: true, minimum: -Infinity, maximum: Infinity }),
2208
+ yAxis: state({ enabled: true, minimum: -Infinity, maximum: Infinity }),
2209
+ });
2210
+ if (type === 'FrameAnimation')
2211
+ Object.assign(value, {
2212
+ running: false,
2213
+ paused: false,
2214
+ frameTime: 0,
2215
+ smoothFrameTime: 0,
2216
+ elapsedTime: 0,
2217
+ currentFrame: 0,
2218
+ start: () => {
2219
+ value.running = true;
2220
+ },
2221
+ stop: () => {
2222
+ value.running = false;
2223
+ },
2224
+ restart: () => {
2225
+ value.elapsedTime = 0;
2226
+ value.currentFrame = 0;
2227
+ value.running = true;
2228
+ },
2229
+ });
2230
+ if (type === 'Image')
2231
+ Object.assign(value, {
2232
+ source: '',
2233
+ sourceSize: state({ width: 0, height: 0 }),
2234
+ sourceClipRect: { x: 0, y: 0, width: 0, height: 0 },
2235
+ fillMode: 0,
2236
+ asynchronous: false,
2237
+ cache: true,
2238
+ status: 0,
2239
+ progress: 0,
2240
+ smooth: true,
2241
+ mipmap: false,
2242
+ paintedWidth: 0,
2243
+ paintedHeight: 0,
2244
+ mirror: false,
2245
+ });
2246
+ if (type === 'Component')
2247
+ Object.assign(value, {
2248
+ status: 1,
2249
+ createObject: (parent, properties = {}) =>
2250
+ instantiate(factories.get(value), parent, properties),
2251
+ errorString: () => '',
2252
+ });
2253
+ if (type === 'Loader')
2254
+ Object.assign(value, {
2255
+ active: true,
2256
+ source: '',
2257
+ sourceComponent: null,
2258
+ asynchronous: false,
2259
+ item: null,
2260
+ status: 0,
2261
+ progress: 0,
2262
+ _reload: 0,
2263
+ setSource: (source, properties = {}) => {
2264
+ value.source = String(source);
2265
+ value._initialProperties = properties;
2266
+ },
2267
+ });
2268
+ if (type === 'Scale')
2269
+ Object.assign(value, {
2270
+ xScale: 1,
2271
+ yScale: 1,
2272
+ origin: state({ x: 0, y: 0 }),
2273
+ });
2274
+ if (type === 'ShapePath')
2275
+ Object.assign(value, {
2276
+ fillGradient: null,
2277
+ fillItem: null,
2278
+ fillRule: 0,
2279
+ strokeColor: 'black',
2280
+ fillColor: 'transparent',
2281
+ strokeWidth: 1,
2282
+ capStyle: 16,
2283
+ joinStyle: 0,
2284
+ strokeStyle: 0,
2285
+ dashPattern: [],
2286
+ dashOffset: 0,
2287
+ });
2288
+ if (type === 'Grid')
2289
+ Object.assign(value, {
2290
+ columns: 4,
2291
+ rows: 0,
2292
+ rowSpacing: null,
2293
+ columnSpacing: null,
2294
+ });
2295
+ if (type === 'PathMultiline') value.paths = [];
2296
+ if (type === 'GridLayout') Object.assign(value, {columns:0,rows:0,columnSpacing:5,rowSpacing:5});
2297
+ if (type === 'PathSvg') value.path = '';
2298
+ if (type === 'LinearGradient')
2299
+ Object.assign(value, { x1: 0, y1: 0, x2: 0, y2: 0 });
2300
+
2301
+ if (type === 'Instantiator') Object.assign(value, {model:1,count:0,active:true,asynchronous:false,object:null,objectAt:()=>null});
2302
+ if (type === 'Repeater')
2303
+ Object.assign(value, { model: [], count: 0, itemAt: () => null });
2304
+ if (type === 'MouseArea')
2305
+ Object.assign(value, {
2306
+ acceptedButtons: 1,
2307
+ hoverEnabled: false,
2308
+ pressed: false,
2309
+ containsMouse: false,
2310
+ mouseX: 0,
2311
+ mouseY: 0,
2312
+ cursorShape: 0,
2313
+ });
2314
+ if (type === 'TapHandler')
2315
+ Object.assign(value, {
2316
+ acceptedButtons: 1,
2317
+ gesturePolicy: 1,
2318
+ tapCount: 0,
2319
+ pressed: false,
2320
+ point: { position: { x: 0, y: 0 }, scenePosition: { x: 0, y: 0 } },
2321
+ });
2322
+ if (type === 'HoverHandler')
2323
+ Object.assign(value, {
2324
+ cursorShape: 0,
2325
+ hovered: false,
2326
+ point: { position: { x: 0, y: 0 }, scenePosition: { x: 0, y: 0 } },
2327
+ });
2328
+ if (type === 'FileView') {
2329
+ Object.assign(value, {
2330
+ path: '',
2331
+ loaded: false,
2332
+ watchChanges: false,
2333
+ blockLoading: false,
2334
+ preload: true,
2335
+ atomicWrites: true,
2336
+ printErrors: true,
2337
+ });
2338
+ let body = '',
2339
+ readPath,
2340
+ pendingWrite,
2341
+ disposed = false;
2342
+ disposers?.push(() => {
2343
+ disposed = true;
2344
+ pendingWrite = null;
2345
+ });
2346
+ const save = () => {
2347
+ if (!pendingWrite || disposed) return;
2348
+ const request = pendingWrite;
2349
+ pendingWrite = null;
2350
+ const result = host.write(
2351
+ request.path,
2352
+ request.data,
2353
+ value.atomicWrites,
2354
+ );
2355
+ if (!result.error) {
2356
+ body =
2357
+ typeof request.data === 'string'
2358
+ ? request.data
2359
+ : new TextDecoder().decode(request.data);
2360
+ readPath = request.path;
2361
+ value.loaded = true;
2362
+ }
2363
+ emit(value, result.error ? 'onSaveFailed' : 'onSaved', result.error);
2364
+ };
2365
+ value.setText = (text) => {
2366
+ pendingWrite = { path: value.path, data: String(text) };
2367
+ queueMicrotask(save);
2368
+ };
2369
+ value.setData = (data) => {
2370
+ pendingWrite = { path: value.path, data: new Uint8Array(data) };
2371
+ queueMicrotask(save);
2372
+ };
2373
+ value.reload = () => {
2374
+ const result = host.read(value.path);
2375
+ readPath = value.path;
2376
+ body = result.text;
2377
+ value.loaded = !result.error;
2378
+ emit(value, result.error ? 'onLoadFailed' : 'onLoaded', result.error);
2379
+ };
2380
+ value.text = () => {
2381
+ if (readPath !== value.path) value.reload();
2382
+ return body;
2383
+ };
2384
+ value.data = () => new TextEncoder().encode(value.text()).buffer;
2385
+ value.waitForJob = () => {
2386
+ if (pendingWrite) save();
2387
+ if (readPath !== value.path) value.reload();
2388
+ };
2389
+ }
2390
+ if (type === 'StdioCollector') Object.assign(value, { text: '', data: new ArrayBuffer(0) });
2391
+ if (type === 'SplitParser') Object.assign(value, { splitMarker: '\n' });
2392
+ if (type === 'Socket') {
2393
+ const status = state({ wanted: false, actual: false });
2394
+ socketStates.set(value, status);
2395
+ Object.assign(value, { path: '', parser: null, error: signal() });
2396
+ alias(value, 'connected', () => status.actual, connected => { status.wanted = !!connected; }, false);
2397
+ }
2398
+ if (type === 'Process')
2399
+ Object.assign(value, {
2400
+ running: false,
2401
+ command: [],
2402
+ stdout: null,
2403
+ stderr: null,
2404
+ stdinEnabled: false,
2405
+ workingDirectory: '',
2406
+ environment: {},
2407
+ clearEnvironment: false,
2408
+ processId: 0,
2409
+ });
2410
+ if (type === 'Timer')
2411
+ Object.assign(value, {
2412
+ interval: 1000,
2413
+ running: false,
2414
+ repeat: false,
2415
+ restart: () => {
2416
+ value.running = true;
2417
+ },
2418
+ start: () => {
2419
+ value.running = true;
2420
+ },
2421
+ stop: () => {
2422
+ value.running = false;
2423
+ },
2424
+ });
2425
+ if (type === 'Connections')
2426
+ Object.assign(value, {
2427
+ target: null,
2428
+ enabled: true,
2429
+ ignoreUnknownSignals: false,
2430
+ });
2431
+ if (type === 'TextMetrics' || type === 'FontMetrics') {
2432
+ Object.assign(value, { elide: 0, elideWidth: 0, ascent: 0, descent: 0 });
2433
+ const measure = (text) =>
2434
+ measureText(
2435
+ `${value.font.bold ? 'bold ' : ''}${value.font.pixelSize}px ${value.font.family}`,
2436
+ text,
2437
+ );
2438
+ const elide = (text, mode, width) => {
2439
+ text = String(text);
2440
+ if (!mode || measure(text).width <= width) return text;
2441
+ if (measure('…').width > width) return '';
2442
+ const chars = Array.from(text),
2443
+ candidate = (n) =>
2444
+ mode === 1
2445
+ ? '…' + chars.slice(chars.length - n).join('')
2446
+ : mode === 3
2447
+ ? chars.slice(0, Math.ceil(n / 2)).join('') +
2448
+ '…' +
2449
+ chars.slice(chars.length - Math.floor(n / 2)).join('')
2450
+ : chars.slice(0, n).join('') + '…';
2451
+ let lo = 0,
2452
+ hi = chars.length;
2453
+ while (lo < hi) {
2454
+ const n = Math.ceil((lo + hi) / 2);
2455
+ if (measure(candidate(n)).width <= width) lo = n;
2456
+ else hi = n - 1;
2457
+ }
2458
+ return candidate(lo);
2459
+ };
2460
+ if (type === 'FontMetrics') value.elidedText = elide;
2461
+ effect(() => {
2462
+ const m = measure(value.text || '0');
2463
+ if (type === 'TextMetrics') {
2464
+ value.advanceWidth = m.width;
2465
+ value.width = m.width;
2466
+ value.elidedText = elide(value.text, value.elide, value.elideWidth);
2467
+ } else value.advanceWidth = (text) => measure(text).width;
2468
+ value.ascent = m.fontBoundingBoxAscent || value.font.pixelSize * 0.8;
2469
+ value.descent = m.fontBoundingBoxDescent || value.font.pixelSize * 0.2;
2470
+ value.height = value.ascent + value.descent;
2471
+ if (type === 'TextMetrics')
2472
+ value.boundingRect = {
2473
+ x: 0,
2474
+ y: -value.ascent,
2475
+ width: m.width,
2476
+ height: value.height,
2477
+ };
2478
+ else
2479
+ value.boundingRect = value.tightBoundingRect = (text) => {
2480
+ const m = measure(text);
2481
+ return {
2482
+ x: 0,
2483
+ y: -(m.actualBoundingBoxAscent || value.ascent),
2484
+ width: m.width,
2485
+ height: value.height,
2486
+ };
2487
+ };
2488
+ value.averageCharacterWidth = measure('0').width;
2489
+ });
2490
+ }
2491
+ media.defaults(value,type);
2492
+ textures.make(value, type, element);
2493
+ canvases.make(value, type, element);
2494
+ shaders.make(value,type,element);
2495
+ shortcuts.make(value, type);
2496
+ shapes.make(value, type, element, parent);
2497
+ models.make(value, type, element, parent);
2498
+ if (nonvisual.has(type)) return value;
2499
+ const grabFrames=new Set();
2500
+ disposers?.push(()=>{for(const frame of grabFrames)cancelAnimationFrame(frame);grabFrames.clear();});
2501
+ value.grabToImage=(callback,size)=>{
2502
+ if(!host||typeof callback!=='function'||!element.isConnected)return false;
2503
+ const w=Math.ceil(size?.width??value.width),h=Math.ceil(size?.height??value.height);
2504
+ if(!(w>0&&h>0)||w>8192||h>8192||w*h>16777216)return false;
2505
+ const frame=requestAnimationFrame(()=>{
2506
+ grabFrames.delete(frame);
2507
+ if(!element.isConnected)return;
2508
+ const canvas=document.createElement('canvas');canvas.width=w;canvas.height=h;
2509
+ try{
2510
+ textures.flush();
2511
+ canvas.getContext('2d').drawImage(element,0,0,w,h);
2512
+ callback({url:canvas.toDataURL('image/png')});
2513
+ }finally{canvas.width=canvas.height=1;}
2514
+ });
2515
+ grabFrames.add(frame);
2516
+ return true;
2517
+ };
2518
+ focusElements.set(element, value);
2519
+ listen(element, 'focus', () => activateFocus(value));
2520
+ listen(element, 'blur', (event) =>
2521
+ activateFocus(focusElements.get(event.relatedTarget) ?? null),
2522
+ );
2523
+ disposers?.push(() => {
2524
+ if (activeFocusValues.has(value)) activateFocus(null);
2525
+ focusElements.delete(element);
2526
+ });
2527
+ effect(() => {
2528
+ if (value.focus && enabled(value))
2529
+ untrack(() => {
2530
+ if (document.activeElement !== element) element.focus();
2531
+ });
2532
+ });
2533
+ effect(() => {
2534
+ element.tabIndex = value.activeFocusOnTab ? 0 : -1;
2535
+ if (value.objectName)
2536
+ element.setAttribute('data-qml-object-name', value.objectName);
2537
+ if (value.Accessible.name)
2538
+ element.setAttribute('aria-label', value.Accessible.name);
2539
+ if (value.Accessible.role)
2540
+ element.setAttribute(
2541
+ 'role',
2542
+ value.Accessible.role === 44 ? 'checkbox' : 'button',
2543
+ );
2544
+ if (value.Accessible.role === 44)
2545
+ element.setAttribute('aria-checked', String(value.Accessible.checked));
2546
+ });
2547
+ if (type === 'Flow') Object.assign(value, { flow: 0, layoutDirection: 0 });
2548
+ if (['Row', 'Column', 'Grid', 'Flow'].includes(type))
2549
+ effect(() => {
2550
+ const children = childrenOf(value).filter(
2551
+ (v) =>
2552
+ v.visible !== false && (v.width || 0) > 0 && (v.height || 0) > 0,
2553
+ );
2554
+ let w = 0,
2555
+ h = 0;
2556
+ const gap = value.spacing || 0;
2557
+ if (type === 'Flow') {
2558
+ const vertical = value.flow === 1,
2559
+ limit = vertical
2560
+ ? value.height - value.topPadding - value.bottomPadding
2561
+ : value.width - value.leftPadding - value.rightPadding;
2562
+ let main = 0,
2563
+ cross = 0,
2564
+ line = 0,
2565
+ maxMain = 0;
2566
+ for (const child of children) {
2567
+ const size = vertical ? child.height : child.width,
2568
+ other = vertical ? child.width : child.height;
2569
+ if (main > 0 && limit > 0 && main + size > limit) {
2570
+ maxMain = Math.max(maxMain, main - gap);
2571
+ cross += line + gap;
2572
+ main = 0;
2573
+ line = 0;
2574
+ }
2575
+ child.x = vertical ? cross : main;
2576
+ child.y = vertical ? main : cross;
2577
+ main += size + gap;
2578
+ line = Math.max(line, other);
2579
+ }
2580
+ maxMain = Math.max(maxMain, main ? main - gap : 0);
2581
+ w = vertical ? cross + line : maxMain;
2582
+ h = vertical ? maxMain : cross + line;
2583
+ } else if (type === 'Grid') {
2584
+ const cols = Math.max(
2585
+ 1,
2586
+ value.columns ||
2587
+ Math.ceil(children.length / Math.max(1, value.rows)),
2588
+ ),
2589
+ cw = [],
2590
+ rh = [];
2591
+ children.forEach((v, i) => {
2592
+ cw[i % cols] = Math.max(cw[i % cols] || 0, v.width);
2593
+ rh[Math.floor(i / cols)] = Math.max(
2594
+ rh[Math.floor(i / cols)] || 0,
2595
+ v.height,
2596
+ );
2597
+ });
2598
+ const gx = value.columnSpacing ?? gap,
2599
+ gy = value.rowSpacing ?? gap;
2600
+ children.forEach((v, i) => {
2601
+ v.x = cw.slice(0, i % cols).reduce((a, b) => a + b + gx, 0);
2602
+ v.y = rh
2603
+ .slice(0, Math.floor(i / cols))
2604
+ .reduce((a, b) => a + b + gy, 0);
2605
+ });
2606
+ w = cw.reduce((a, b) => a + b, 0) + Math.max(0, cw.length - 1) * gx;
2607
+ h = rh.reduce((a, b) => a + b, 0) + Math.max(0, rh.length - 1) * gy;
2608
+ } else
2609
+ for (const child of children) {
2610
+ if (type === 'Row') {
2611
+ child.x = w;
2612
+ w += child.width + gap;
2613
+ h = Math.max(h, child.height);
2614
+ } else {
2615
+ child.y = h;
2616
+ h += child.height + gap;
2617
+ w = Math.max(w, child.width);
2618
+ }
2619
+ }
2620
+ if (type === 'Row' && children.length) w -= gap;
2621
+ if (type === 'Column' && children.length) h -= gap;
2622
+ for (const child of children) {
2623
+ if (type !== 'Column') child.x += value.leftPadding;
2624
+ else if (!descriptions.get(child)?.properties.includes('x'))
2625
+ child.x = value.leftPadding;
2626
+ if (type !== 'Row') child.y += value.topPadding;
2627
+ else if (!descriptions.get(child)?.properties.includes('y'))
2628
+ child.y = value.topPadding;
2629
+ }
2630
+ value.implicitWidth = w + value.leftPadding + value.rightPadding;
2631
+ value.implicitHeight = h + value.topPadding + value.bottomPadding;
2632
+ });
2633
+ if (type === 'GridLayout')
2634
+ effect(() => {
2635
+ const items=childrenOf(value).filter(c=>c.visible!==false);
2636
+ const cols=Math.max(1, value.columns || (value.rows ? Math.ceil(items.length/value.rows) : items.length));
2637
+ const rows=Math.ceil(items.length/cols), widths=Array(cols).fill(0), heights=Array(rows).fill(0);
2638
+ const preferred=(c,axis)=>Math.max(c.Layout['minimum'+axis],Math.min(c.Layout['maximum'+axis],layoutPreferred(c,axis)));
2639
+ items.forEach((c,i)=>{widths[i%cols]=Math.max(widths[i%cols],preferred(c,'Width'));heights[Math.floor(i/cols)]=Math.max(heights[Math.floor(i/cols)],preferred(c,'Height'));});
2640
+ value.implicitWidth=widths.reduce((a,b)=>a+b,0)+Math.max(0,cols-1)*value.columnSpacing;
2641
+ value.implicitHeight=heights.reduce((a,b)=>a+b,0)+Math.max(0,rows-1)*value.rowSpacing;
2642
+ for(const [sizes,axis,available,gap] of [[widths,'Width',value.width,value.columnSpacing],[heights,'Height',value.height,value.rowSpacing]]){
2643
+ const tracks=new Set(items.flatMap((c,i)=>c.Layout['fill'+axis]?[axis==='Width'?i%cols:Math.floor(i/cols)]:[]));
2644
+ let extra=available-sizes.reduce((a,b)=>a+b,0)-Math.max(0,sizes.length-1)*gap;
2645
+ const minimum=sizes.map((_,index)=>Math.max(0,...items.filter((c,i)=>(axis==='Width'?i%cols:Math.floor(i/cols))===index).map(c=>c.Layout['minimum'+axis])));
2646
+ for(let pass=0;pass<sizes.length && Math.abs(extra)>0.01;pass++){
2647
+ const adjustable=[...tracks].filter(index=>extra>0||sizes[index]>minimum[index]);
2648
+ if(!adjustable.length)break;
2649
+ const share=extra/adjustable.length;let used=0;
2650
+ for(const index of adjustable){const next=Math.max(minimum[index],sizes[index]+share);used+=next-sizes[index];sizes[index]=next;}
2651
+ extra-=used;
2652
+ }
2653
+ }
2654
+ items.forEach((c,i)=>{
2655
+ const col=i%cols,row=Math.floor(i/cols),l=c.Layout;
2656
+ c.width=l.fillWidth?Math.max(l.minimumWidth,Math.min(l.maximumWidth,widths[col])):preferred(c,'Width');
2657
+ c.height=l.fillHeight?Math.max(l.minimumHeight,Math.min(l.maximumHeight,heights[row])):preferred(c,'Height');
2658
+ c.x=widths.slice(0,col).reduce((a,b)=>a+b+value.columnSpacing,0)+(l.alignment&2?widths[col]-c.width:l.alignment&4?(widths[col]-c.width)/2:0);
2659
+ c.y=heights.slice(0,row).reduce((a,b)=>a+b+value.rowSpacing,0)+(l.alignment&64?heights[row]-c.height:l.alignment&128?(heights[row]-c.height)/2:0);
2660
+ });
2661
+ });
2662
+ if (type === 'RowLayout' || type === 'ColumnLayout')
2663
+ effect(() => {
2664
+ const horizontal = type === 'RowLayout',
2665
+ main = horizontal ? 'width' : 'height',
2666
+ cross = horizontal ? 'height' : 'width',
2667
+ axis = horizontal ? 'x' : 'y',
2668
+ other = horizontal ? 'y' : 'x',
2669
+ suffix = horizontal ? 'Width' : 'Height',
2670
+ opposite = horizontal ? 'Height' : 'Width';
2671
+ const children = childrenOf(value).filter((c) => c.visible !== false),
2672
+ gap = value.spacing;
2673
+ const boxes = children.map((c) => {
2674
+ const l = c.Layout;
2675
+ const preferred = layoutPreferred(c, suffix);
2676
+ return {
2677
+ c,
2678
+ l,
2679
+ min: l['minimum' + suffix],
2680
+ max: l['maximum' + suffix],
2681
+ size: Math.max(
2682
+ l['minimum' + suffix],
2683
+ Math.min(l['maximum' + suffix], preferred),
2684
+ ),
2685
+ fill: l['fill' + suffix],
2686
+ };
2687
+ });
2688
+ const gaps = Math.max(0, boxes.length - 1) * gap,
2689
+ preferred = boxes.reduce((sum, b) => sum + b.size, gaps);
2690
+ const available = Math.max(0, value[main] - gaps);
2691
+ let remaining = available - boxes.reduce((sum, b) => sum + b.size, 0);
2692
+ for (let n = 0; n <= boxes.length && Math.abs(remaining) > 0.01; n++) {
2693
+ const adjustable = boxes.filter(
2694
+ (b) => b.fill && (remaining > 0 ? b.size < b.max : b.size > b.min),
2695
+ );
2696
+ if (!adjustable.length) break;
2697
+ const share = remaining / adjustable.length;
2698
+ let used = 0;
2699
+ for (const b of adjustable) {
2700
+ const next = Math.max(b.min, Math.min(b.max, b.size + share));
2701
+ used += next - b.size;
2702
+ b.size = next;
2703
+ }
2704
+ remaining -= used;
2705
+ }
2706
+ let offset = 0,
2707
+ maxCross = 0;
2708
+ const baseline = Math.max(
2709
+ 0,
2710
+ ...boxes
2711
+ .filter((b) => b.l.alignment & 256)
2712
+ .map((b) => b.c.baselineOffset),
2713
+ );
2714
+ for (const b of boxes) {
2715
+ const { c, l } = b;
2716
+ const preferredCross = layoutPreferred(c, opposite);
2717
+ const size = Math.max(
2718
+ l['minimum' + opposite],
2719
+ Math.min(
2720
+ l['maximum' + opposite],
2721
+ l['fill' + opposite] ? value[cross] : preferredCross,
2722
+ ),
2723
+ );
2724
+ c[main] = b.size;
2725
+ c[cross] = size;
2726
+ c[axis] = offset;
2727
+ offset += b.size + gap;
2728
+ const alignment = l.alignment;
2729
+ c[other] =
2730
+ horizontal && alignment & 256
2731
+ ? baseline - c.baselineOffset
2732
+ : alignment & (horizontal ? 64 : 2)
2733
+ ? value[cross] - size
2734
+ : alignment & (horizontal ? 128 : 4)
2735
+ ? (value[cross] - size) / 2
2736
+ : 0;
2737
+ // Intrinsic size must not depend on the space allocated by a parent
2738
+ // layout: that feeds stretched sizes back into sibling allocation.
2739
+ const intrinsic = Math.max(l['minimum' + opposite], Math.min(l['maximum' + opposite], preferredCross));
2740
+ maxCross = Math.max(maxCross, intrinsic + (horizontal && alignment & 256 ? baseline - c.baselineOffset : 0));
2741
+ }
2742
+ value['implicit' + suffix] = preferred;
2743
+ value['implicit' + opposite] = maxCross;
2744
+ });
2745
+ if (
2746
+ [
2747
+ 'Text',
2748
+ 'Label',
2749
+ 'Button',
2750
+ 'TextField',
2751
+ 'TextInput',
2752
+ 'TextEdit',
2753
+ ].includes(type)
2754
+ ) {
2755
+ effect(() => {
2756
+ const font = `${value.font.bold ? 'bold ' : ''}${value.font.pixelSize}px ${value.font.family}`;
2757
+ const control = type === 'TextField' || type === 'Button';
2758
+ const content = ['Text', 'Label'].includes(type)
2759
+ ? textContent(value).plain
2760
+ : String(value.text);
2761
+ const metrics = measureText(font, content),
2762
+ explicit = descriptions.get(value)?.properties ?? [];
2763
+ const line =
2764
+ metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent ||
2765
+ value.font.pixelSize;
2766
+ const measure = (text) =>
2767
+ measureText(font, text).width +
2768
+ Math.max(0, Array.from(text).length - 1) * value.font.letterSpacing;
2769
+ const paragraphs = content.split('\n'),
2770
+ natural = Math.max(0, ...paragraphs.map(measure));
2771
+ const available = Math.max(
2772
+ 0,
2773
+ value.width - value.leftPadding - value.rightPadding,
2774
+ ),
2775
+ lines = [];
2776
+ for (const paragraph of paragraphs) {
2777
+ if (!value.wrapMode || !available) {
2778
+ lines.push(paragraph);
2779
+ continue;
2780
+ }
2781
+ const pieces =
2782
+ value.wrapMode === 2
2783
+ ? Array.from(paragraph)
2784
+ : paragraph.match(/\S+\s*|\s+/g) || [''];
2785
+ let current = '';
2786
+ for (const piece of pieces) {
2787
+ if (current && measure(current + piece) > available) {
2788
+ lines.push(current);
2789
+ current = '';
2790
+ }
2791
+ if (value.wrapMode === 3 && measure(piece) > available) {
2792
+ for (const char of Array.from(piece)) {
2793
+ if (current && measure(current + char) > available) {
2794
+ lines.push(current);
2795
+ current = '';
2796
+ }
2797
+ current += char;
2798
+ }
2799
+ } else current += piece;
2800
+ }
2801
+ lines.push(current);
2802
+ }
2803
+ const count = Math.min(lines.length, value.maximumLineCount),
2804
+ lineBox =
2805
+ value.lineHeightMode === 1
2806
+ ? value.lineHeight
2807
+ : line * value.lineHeight;
2808
+ // A custom Control contentItem supplies its natural size. Its actual
2809
+ // width is assigned by the control, so reading width here would create
2810
+ // a layout cycle and collapse adjacent RowLayout items onto its text.
2811
+ const custom = type === 'Button' && value.contentItem;
2812
+ const padding = Math.max(0, Number(value.padding) || 0);
2813
+ if (!explicit.includes('implicitWidth'))
2814
+ value.implicitWidth = custom
2815
+ ? Math.max(value.background?.implicitWidth || 0, (custom.implicitWidth || 0) + padding * 2)
2816
+ : natural + (control ? 22 : value.leftPadding + value.rightPadding);
2817
+ if (!explicit.includes('implicitHeight'))
2818
+ value.implicitHeight = custom
2819
+ ? Math.max(value.background?.implicitHeight || 0, (custom.implicitHeight || 0) + padding * 2)
2820
+ : count * lineBox + (control ? 18 : value.topPadding + value.bottomPadding);
2821
+ value.baselineOffset =
2822
+ (metrics.fontBoundingBoxAscent || value.font.pixelSize * 0.8) +
2823
+ value.topPadding;
2824
+ value.contentWidth = Math.max(0, ...lines.slice(0, count).map(measure));
2825
+ value.contentHeight = count * lineBox;
2826
+ value.lineCount = count;
2827
+ value.truncated =
2828
+ lines.length > count ||
2829
+ (!!value.elide &&
2830
+ lines.slice(0, count).some((line) => measure(line) > available));
2831
+ });
2832
+ }
2833
+ if (['TextField', 'TextInput', 'TextEdit'].includes(type)) {
2834
+ Object.assign(value, {
2835
+ cursorPosition: 0,
2836
+ selectionStart: 0,
2837
+ selectionEnd: 0,
2838
+ selectedText: '',
2839
+ passwordCharacter: '●',
2840
+ echoMode: 0,
2841
+ maximumLength: 32767,
2842
+ inputMethodComposing: false,
2843
+ acceptableInput: true,
2844
+ autoScroll: true,
2845
+ persistentSelection: false,
2846
+ cursorVisible: true,
2847
+ cursorDelegate:null,
2848
+ selectionColor: '#3874f2',
2849
+ selectedTextColor: 'white',
2850
+ });
2851
+ const selection = () => {
2852
+ value.cursorPosition = element.selectionDirection === 'backward'
2853
+ ? element.selectionStart ?? 0 : element.selectionEnd ?? 0;
2854
+ value.selectionStart = element.selectionStart ?? 0;
2855
+ value.selectionEnd = element.selectionEnd ?? 0;
2856
+ value.selectedText = String(value.text).slice(
2857
+ value.selectionStart,
2858
+ value.selectionEnd,
2859
+ );
2860
+ };
2861
+ value.select = (start, end) => {
2862
+ if (element.value !== String(value.text))
2863
+ element.value = String(value.text);
2864
+ const length = element.value.length;
2865
+ start = Math.max(0, Math.min(length, start));
2866
+ end = Math.max(0, Math.min(length, end));
2867
+ element.selectionDirection = end < start ? 'backward' : 'forward';
2868
+ element.selectionStart = Math.min(start, end);
2869
+ element.selectionEnd = Math.max(start, end);
2870
+ selection();
2871
+ };
2872
+ for(const operation of ['copy','cut','paste'])
2873
+ value[operation] = () => host.inputClipboard(element,operation);
2874
+ value.selectAll = () => value.select(0, String(value.text).length);
2875
+ value.deselect = () =>
2876
+ value.select(value.cursorPosition, value.cursorPosition);
2877
+ value.insert = (at, text) => {
2878
+ const old = String(value.text);
2879
+ value.text = old.slice(0, at) + String(text) + old.slice(at);
2880
+ element.value = value.text;
2881
+ value.select(at + String(text).length, at + String(text).length);
2882
+ };
2883
+ value.remove = (start, end) => {
2884
+ const old = String(value.text);
2885
+ value.text = old.slice(0, start) + old.slice(end);
2886
+ element.value = value.text;
2887
+ value.select(start, start);
2888
+ };
2889
+ const advance = (at) =>
2890
+ measureText(
2891
+ `${value.font.bold ? 'bold ' : ''}${value.font.pixelSize}px ${value.font.family}`,
2892
+ String(value.text).slice(0, at),
2893
+ ).width;
2894
+ value.positionAt = (x) => {
2895
+ const text = String(value.text);
2896
+ let lo = 0,
2897
+ hi = text.length;
2898
+ while (lo < hi) {
2899
+ const mid = (lo + hi) >> 1;
2900
+ if (advance(mid) < x) lo = mid + 1;
2901
+ else hi = mid;
2902
+ }
2903
+ return lo > 0 && x - advance(lo - 1) < advance(lo) - x ? lo - 1 : lo;
2904
+ };
2905
+ value.positionToRectangle = (at) => ({
2906
+ x: advance(at),
2907
+ y: 0,
2908
+ width: 1,
2909
+ height: value.font.pixelSize,
2910
+ });
2911
+ Object.defineProperty(value, 'cursorRectangle', {
2912
+ get: () => value.positionToRectangle(value.cursorPosition),
2913
+ });
2914
+ effect(() => {
2915
+ element.maxLength = value.maximumLength;
2916
+ if (type !== 'TextEdit')
2917
+ element.type =
2918
+ value.echoMode === 2 || value.echoMode === 3 ? 'password' : 'text';
2919
+ if (
2920
+ element.selectionEnd !== value.cursorPosition &&
2921
+ value.selectionStart === value.selectionEnd
2922
+ )
2923
+ element.selectionStart = element.selectionEnd = value.cursorPosition;
2924
+ });
2925
+ let cursorItem=null,cursorFactory=null;
2926
+ effect(()=>{
2927
+ const factory=value.cursorDelegate;
2928
+ if(factory!==cursorFactory)untrack(()=>{
2929
+ cursorItem?.destroy();cursorItem=null;cursorFactory=factory;
2930
+ if(factory)cursorItem=factory.createObject(value);
2931
+ });
2932
+ if(cursorItem){
2933
+ const rect=value.cursorRectangle;
2934
+ cursorItem.x=rect.x+value.leftPadding;cursorItem.y=rect.y+value.topPadding;cursorItem.height=rect.height;
2935
+ cursorItem.visible=value.cursorVisible&&value.activeFocus;
2936
+ }
2937
+ });
2938
+ disposers?.push(()=>cursorItem?.destroy());
2939
+ effect(() => {
2940
+ // Presentation options only; editing and blinking belong to HTML controls.
2941
+ host?.inputPresentation?.(element, {
2942
+ persistent:value.persistentSelection, visible:value.cursorVisible&&!value.cursorDelegate,
2943
+ background:cssColor(value.selectionColor), selectedColor:cssColor(value.selectedTextColor),
2944
+ color:cssColor(value.color), height:value.font.pixelSize,
2945
+ });
2946
+ });
2947
+ for (const event of ['select', 'keyup', 'mouseup'])
2948
+ listen(element, event, selection);
2949
+ listen(element, 'compositionstart', () => {
2950
+ value.inputMethodComposing = true;
2951
+ });
2952
+ listen(element, 'compositionend', () => {
2953
+ value.inputMethodComposing = false;
2954
+ });
2955
+ listen(element, 'blur', () => emit(value, 'onEditingFinished'));
2956
+
2957
+ element.addEventListener('input', () => {
2958
+ value.text = element.value;
2959
+ selection();
2960
+ emit(value, 'onTextEdited');
2961
+ });
2962
+
2963
+ }
2964
+ if(type==='Button'){
2965
+ Object.assign(value,{padding:8,contentItem:null,background:null,focusPolicy:3,hovered:false,pressed:false,down:false});
2966
+ listen(element,'mouseenter',()=>{value.hovered=true;});
2967
+ listen(element,'mouseleave',()=>{value.hovered=false;value.pressed=false;value.down=false;});
2968
+ listen(element,'mousedown',e=>{if(e.button===0&&enabled(value)){value.pressed=true;value.down=true;}if(value.focusPolicy===0)e.preventDefault();});
2969
+ listen(document,'mouseup',()=>{value.pressed=false;value.down=false;});
2970
+ effect(()=>{
2971
+ const pad=Math.max(0,Number(value.padding)||0),content=value.contentItem,bg=value.background;
2972
+ if(content){content.x=pad;content.y=pad;content.width=Math.max(0,value.width-pad*2);content.height=Math.max(0,value.height-pad*2);}
2973
+ if(bg){bg.x=bg.y=0;bg.width=value.width;bg.height=value.height;bg.z=-1;}
2974
+ value.activeFocusOnTab=!!(value.focusPolicy&1);
2975
+ });
2976
+ }
2977
+ if (type === 'Button')
2978
+ element.addEventListener('keydown', (event) => {
2979
+ if (
2980
+ !event.ctrlKey &&
2981
+ !event.altKey &&
2982
+ !event.metaKey &&
2983
+ ['Enter', ' '].includes(event.key)
2984
+ ) {
2985
+ event.preventDefault();
2986
+ if (value.enabled) element.click();
2987
+ }
2988
+ });
2989
+ const textNode = ['Text', 'Label', 'Button'].includes(type)
2990
+ ? document.createElement('span')
2991
+ : null;
2992
+ if (textNode) {
2993
+ textNode.style.display = 'contents';
2994
+ element.insertBefore(textNode, element.firstChild);
2995
+ listen(element, 'click', (event) => {
2996
+ const a = event.target?.closest?.('a[href]');
2997
+ if (a && element.contains(a)) {
2998
+ event.preventDefault();
2999
+ if (enabled(value))
3000
+ emit(value, 'onLinkActivated', a.getAttribute('href'));
3001
+ }
3002
+ });
3003
+ }
3004
+ let paintedStyles = {},
3005
+ paintedCss = null;
3006
+ node.paint = () => {
3007
+ // Compute final styles before touching the DOM: overrides within this
3008
+ // pass must not repeatedly invalidate style/layout or flip values back.
3009
+ const s = {},
3010
+ width = value.width,
3011
+ height = value.height;
3012
+ s.position = 'absolute';
3013
+ s.boxSizing = 'border-box';
3014
+ s.width = width == null ? 'auto' : Math.max(0, width) + 'px';
3015
+ s.height = height == null ? 'auto' : Math.max(0, height) + 'px';
3016
+ s.left = value.x + 'px';
3017
+ s.top = value.y + 'px';
3018
+ const textureSource = textures.keepLayout(value);
3019
+ s.display = value.visible || textureSource ? 'block' : 'none';
3020
+ const transform = value.transform;
3021
+ s.transformOrigin = transform ? '0 0' : '50% 50%';
3022
+ s.transform =
3023
+ [
3024
+ value.scale !== 1 ? 'scale(' + value.scale + ')' : '',
3025
+ value.rotation ? 'rotate(' + value.rotation + 'deg)' : '',
3026
+ transform?.xScale !== undefined
3027
+ ? 'scale(' + transform.xScale + ',' + transform.yScale + ')'
3028
+ : transform
3029
+ ? 'translate(' +
3030
+ (transform.x || 0) +
3031
+ 'px,' +
3032
+ (transform.y || 0) +
3033
+ 'px)'
3034
+ : '',
3035
+ ]
3036
+ .filter(Boolean)
3037
+ .join(' ') || 'none';
3038
+ s.zIndex = String(value.z);
3039
+ s.opacity = String(value.opacity);
3040
+ const children = childSet(value);
3041
+ if (children) children.version.value;
3042
+ const mouseHandler = [...(children?.items ?? [])].some(
3043
+ (child) => owners.get(child)?.type === 'TapHandler' && child.enabled,
3044
+ );
3045
+ const clickable =
3046
+ handlers.get(value)?.has('onClicked') ||
3047
+ mouseHandler ||
3048
+ [
3049
+ 'Button',
3050
+ 'TextField',
3051
+ 'TextInput',
3052
+ 'TextEdit',
3053
+ 'FloatingWindow',
3054
+ 'Flickable',
3055
+ 'ListView',
3056
+ 'GridView',
3057
+ ].includes(type) ||
3058
+ (type === 'MouseArea' && !value.propagateComposedEvents);
3059
+ s.pointerEvents = value.enabled && clickable ? 'auto' : 'none';
3060
+ s.overflow = value.clip ? 'hidden' : 'visible';
3061
+ if (
3062
+ [
3063
+ 'Text',
3064
+ 'Label',
3065
+ 'TextField',
3066
+ 'TextInput',
3067
+ 'TextEdit',
3068
+ 'Button',
3069
+ ].includes(type)
3070
+ ) {
3071
+ if (['TextField', 'TextInput', 'TextEdit'].includes(type)) {
3072
+ // Do not reset the caret/selection on unrelated reactive updates.
3073
+ if (element.value !== String(value.text))
3074
+ element.value = String(value.text);
3075
+ element.readOnly = value.readOnly;
3076
+ element.placeholder = value.placeholderText;
3077
+ s.userSelect = value.selectByMouse ? 'text' : 'none';
3078
+ if (type !== 'TextField') {
3079
+ s.backgroundColor = 'transparent';
3080
+ s.border = 'none';
3081
+ s.outline = 'none';
3082
+ s.padding = `${value.topPadding || 0}px ${value.rightPadding || 0}px ${value.bottomPadding || 0}px ${value.leftPadding || 0}px`;
3083
+ }
3084
+ } else if (textNode) {
3085
+ if (['Text', 'Label'].includes(type)) {
3086
+ const content = textContent(value);
3087
+ if (textNode.firstChild !== content.container)
3088
+ textNode.replaceChildren(content.container);
3089
+ for (const a of content.container.querySelectorAll('a'))
3090
+ a.style.color = cssColor(value.linkColor);
3091
+ } else textNode.textContent = String(value.text);
3092
+ }
3093
+ if (type === 'TextField' || type === 'Button') {
3094
+ element.disabled = !value.enabled;
3095
+ s.padding = type==='Button'?Math.max(0,Number(value.padding)||0)+'px':'8px 10px';
3096
+ s.backgroundColor = type === 'Button' ? '#bcebf0' : '#ffffff';
3097
+ s.border = '1px solid #6a8791';
3098
+ s.borderRadius = '5px';
3099
+ element.setAttribute(
3100
+ 'aria-label',
3101
+ value.Accessible.name || String(value.text),
3102
+ );
3103
+ }
3104
+ s.color = cssColor(value.color);
3105
+ s.fontSize = `${value.font.pixelSize}px`;
3106
+ s.fontFamily = value.font.family;
3107
+ s.fontWeight = value.font.bold ? 'bold' : 'normal';
3108
+ s.fontStyle = value.font.italic ? 'italic' : 'normal';
3109
+ s.letterSpacing = value.font.letterSpacing + 'px';
3110
+ s.textTransform =
3111
+ { 1: 'uppercase', 2: 'lowercase', 4: 'capitalize' }[
3112
+ value.font.capitalization
3113
+ ] || 'none';
3114
+ s.fontVariant =
3115
+ value.font.capitalization === 3 ? 'small-caps' : 'normal';
3116
+ s.whiteSpace = value.wrapMode ? 'pre-wrap' : 'pre';
3117
+ s.overflowWrap =
3118
+ value.wrapMode === 2 || value.wrapMode === 3 ? 'anywhere' : 'normal';
3119
+ // CSS numeric line-height multiplies font-size, whereas QML's
3120
+ // proportional mode multiplies the measured font line box (including
3121
+ // fallback glyphs). Use the same line extent as implicitHeight.
3122
+ s.lineHeight = (value.lineHeightMode === 1 ? value.lineHeight :
3123
+ value.lineCount > 0 ? value.contentHeight / value.lineCount :
3124
+ value.font.pixelSize * value.lineHeight) + 'px';
3125
+ s.textOverflow = value.elide ? 'ellipsis' : 'clip';
3126
+ if (value.elide) {
3127
+ s.overflow = 'hidden';
3128
+ s.textOverflow = 'ellipsis';
3129
+ if (!value.wrapMode && !String(value.text).includes('\n'))
3130
+ s.whiteSpace = 'nowrap';
3131
+ }
3132
+ if (value.verticalAlignment === 128 && !value.wrapMode && height > 0)
3133
+ s.lineHeight = height + 'px';
3134
+ s.textAlign =
3135
+ value.horizontalAlignment === 4 || type === 'Button'
3136
+ ? 'center'
3137
+ : value.horizontalAlignment === 2
3138
+ ? 'right'
3139
+ : 'left';
3140
+ } else {
3141
+ s.backgroundColor = ['Rectangle', 'FloatingWindow'].includes(type)
3142
+ ? cssColor(value.color)
3143
+ : 'transparent';
3144
+ s.borderRadius = `${value.radius}px`;
3145
+ s.border = `${value.border.width}px solid ${cssColor(value.border.color)}`;
3146
+ }
3147
+ if (type === 'ShapePath') {
3148
+ for (const [key, v] of Object.entries({
3149
+ stroke: cssColor(value.strokeColor),
3150
+ fill: cssColor(value.fillColor),
3151
+ 'stroke-width': value.strokeWidth,
3152
+ 'stroke-linecap':
3153
+ value.capStyle === 32
3154
+ ? 'round'
3155
+ : value.capStyle === 16
3156
+ ? 'square'
3157
+ : 'butt',
3158
+ 'stroke-linejoin':
3159
+ value.joinStyle === 128
3160
+ ? 'round'
3161
+ : value.joinStyle === 64
3162
+ ? 'bevel'
3163
+ : 'miter',
3164
+ 'stroke-dasharray':
3165
+ value.strokeStyle === 2
3166
+ ? value.dashPattern.map((n) => n * value.strokeWidth).join(' ')
3167
+ : 'none',
3168
+ 'stroke-dashoffset': value.dashOffset * value.strokeWidth,
3169
+ 'fill-rule': value.fillRule === 0 ? 'evenodd' : 'nonzero',
3170
+ }))
3171
+ element.setAttribute(key, String(v));
3172
+ }
3173
+ if (type === 'PathMultiline') element.setAttribute('d', shapes.multiline(value.paths));
3174
+ if (type === 'PathSvg') element.setAttribute('d', value.path);
3175
+ if (type === 'PathRectangle')
3176
+ for (const [key, v] of Object.entries({
3177
+ x: value.x,
3178
+ y: value.y,
3179
+ width: value.width,
3180
+ height: value.height,
3181
+ rx: value.radius,
3182
+ ry: value.radius,
3183
+ }))
3184
+ element.setAttribute(key, String(v ?? 0));
3185
+ if (type === 'Rectangle') s.backgroundImage = 'none';
3186
+ if (type === 'Rectangle' && value.gradient) {
3187
+ const stops = nodes
3188
+ .filter((n) => parents.get(n.value) === value.gradient)
3189
+ .map((n) => n.value);
3190
+ s.backgroundImage =
3191
+ 'linear-gradient(' +
3192
+ (value.gradient.orientation === 1 ? 'to right' : 'to bottom') +
3193
+ ',' +
3194
+ stops
3195
+ .map((v) => cssColor(v.color) + ' ' + v.position * 100 + '%')
3196
+ .join(',') +
3197
+ ')';
3198
+ }
3199
+
3200
+ if(type==='Button'){
3201
+ if(value.background){s.backgroundColor='transparent';s.border='none';s.borderRadius='0';s.isolation='isolate';}
3202
+ if(textNode)textNode.style.display=value.contentItem?'none':'contents';
3203
+ }
3204
+ const style = element.style;
3205
+ // Reuse our input values (CSS getters may normalize colors/shorthands).
3206
+ // If another QML helper or script changed inline styles, reconcile them.
3207
+ const unchanged = paintedCss === style.cssText;
3208
+ for (const [key, next] of Object.entries(s)) {
3209
+ const text = String(next);
3210
+ if (unchanged ? paintedStyles[key] !== text : style[key] !== text)
3211
+ style[key] = text;
3212
+ s[key] = text;
3213
+ }
3214
+ paintedStyles = s;
3215
+ paintedCss = style.cssText;
3216
+
3217
+ shapes.paint(value, type);
3218
+ if (type === 'FloatingWindow' && value.title)
3219
+ document.title = String(value.title);
3220
+ };
3221
+ return value;
3222
+ };
3223
+ // Bind through a setter supplied by generated code. A separate flag avoids
3224
+ // treating a binding's own write as an imperative assignment.
3225
+ const bind = (target, key, read, readonly = false, type = null) => {
3226
+ if (socketStates.has(target) && key === 'connected') return bindAlias(target, key, read);
3227
+ if (key === 'parent' && owners.has(target)) {
3228
+ initial.push(() => {
3229
+ const previous = disposers;
3230
+ disposers = owners.get(target).scope;
3231
+ try {
3232
+ effect(() => {
3233
+ target.parent = read();
3234
+ });
3235
+ } finally {
3236
+ disposers = previous;
3237
+ }
3238
+ });
3239
+ return;
3240
+ }
3241
+ let writing = false;
3242
+ let stop;
3243
+ let cached = target[key];
3244
+ Object.defineProperty(target, key, {
3245
+ configurable: true,
3246
+ enumerable: true,
3247
+ get() {
3248
+ if (!writing && stop?.dirty) stop.run();
3249
+ return cached;
3250
+ },
3251
+ set(value) {
3252
+ const binding = Qt.bindingValue(value);
3253
+ if (!writing && binding) {
3254
+ if (readonly) throw new TypeError(`Readonly QML property: ${key}`);
3255
+ stop?.();
3256
+ bind(target, key, binding, false, type);
3257
+ untrack(start);
3258
+ return;
3259
+ }
3260
+ if (!writing && !animationWrite && readonly)
3261
+ throw new TypeError(`Readonly QML property: ${key}`);
3262
+ if (!writing && !animationWrite) stop?.();
3263
+ const behavior = behaviors.get(target)?.get(key);
3264
+ if (
3265
+ !animationWrite &&
3266
+ behavior?.value.enabled &&
3267
+ started.has(behavior.value) &&
3268
+ Number.isFinite(cached) &&
3269
+ Number.isFinite(value) &&
3270
+ cached !== value
3271
+ ) {
3272
+ animate(behavior.animation, () => {}, {
3273
+ target,
3274
+ key,
3275
+ from: cached,
3276
+ to: value,
3277
+ });
3278
+ return;
3279
+ }
3280
+ cached = type === 'color' ? Qt.color(value) : value;
3281
+ propertyWrites.get(target)?.get(key)?.();
3282
+ },
3283
+ });
3284
+ const owner = owners.get(target),
3285
+ scope = owner ? owner.scope : disposers;
3286
+ initial.push(() => {
3287
+ const previous = disposers;
3288
+ disposers = scope;
3289
+ try {
3290
+ stop = effect(() => {
3291
+ const value = read();
3292
+ // A parent's layout can run before this explicit size binding's
3293
+ // first evaluation. Replace that provisional zero with the declared
3294
+ // size, and invalidate the layout through reactive hint storage.
3295
+ if ((key === 'width' || key === 'height') && descriptions.get(target)?.properties.includes(key)) {
3296
+ let hint = layoutFallbackSizes.get(target);
3297
+ if (!hint) layoutFallbackSizes.set(target, hint = state({}));
3298
+ hint[key === 'width' ? 'Width' : 'Height'] = Math.max(0, Number(value) || 0);
3299
+ }
3300
+ writing = true;
3301
+ try {
3302
+ target[key] = value;
3303
+ } finally {
3304
+ writing = false;
3305
+ }
3306
+ });
3307
+ stop.onInvalidate = () => reactive.invalidate(target, key);
3308
+ } catch (error) {
3309
+ const wrapped = Error(
3310
+ 'QML binding ' +
3311
+ (owners.get(target)?.type || 'object') +
3312
+ '.' +
3313
+ key +
3314
+ ' (' +
3315
+ (descriptions.get(target)?.url || '') +
3316
+ '): ' +
3317
+ error.message,
3318
+ { cause: error },
3319
+ );
3320
+ wrapped.qmlFatal = error.qmlFatal;
3321
+ throw wrapped;
3322
+ } finally {
3323
+ disposers = previous;
3324
+ }
3325
+ });
3326
+ };
3327
+ const alias = (target, key, read, write, readonly) => {
3328
+ let keys = aliases.get(target);
3329
+ if (!keys) aliases.set(target, (keys = new Map()));
3330
+ const record = { stop: null, updating: false, version: 0 };
3331
+ keys.set(key, record);
3332
+ return Object.defineProperty(target, key, {
3333
+ configurable: true,
3334
+ enumerable: true,
3335
+ get() {
3336
+ if (!record.updating && record.stop?.dirty) record.stop.run();
3337
+ return read();
3338
+ },
3339
+ set(value) {
3340
+ if (readonly) throw new TypeError('Readonly QML alias: ' + key);
3341
+ const binding = Qt.bindingValue(value);
3342
+ if (!record.updating && binding) {
3343
+ bindAlias(target, key, binding);
3344
+ untrack(start);
3345
+ return;
3346
+ }
3347
+ if (!record.updating) {
3348
+ record.version++;
3349
+ record.stop?.();
3350
+ record.stop = null;
3351
+ }
3352
+ write(value);
3353
+ },
3354
+ });
3355
+ };
3356
+ const bindAlias = (target, key, read) => {
3357
+ const record = aliases.get(target)?.get(key);
3358
+ if (!record) throw Error('Unknown QML alias: ' + key);
3359
+ record.stop?.();
3360
+ const version = ++record.version,
3361
+ scope = owners.get(target)?.scope;
3362
+ initial.push(() => {
3363
+ if (version !== record.version) return;
3364
+ const previous = disposers;
3365
+ disposers = scope;
3366
+ try {
3367
+ record.stop = effect(() => {
3368
+ const next = read();
3369
+ untrack(() => {
3370
+ record.updating = true;
3371
+ try {
3372
+ target[key] = next;
3373
+ } finally {
3374
+ record.updating = false;
3375
+ }
3376
+ });
3377
+ });
3378
+ record.stop.onInvalidate = () => reactive.invalidate(target, key);
3379
+ } finally {
3380
+ disposers = previous;
3381
+ }
3382
+ });
3383
+ };
3384
+ const create = (type, index, parent, before, persistent = false) => {
3385
+ const svgTag = { ShapePath: 'g', PathMultiline: 'path', PathSvg: 'path', PathRectangle: 'rect' }[
3386
+ type
3387
+ ];
3388
+ const element = svgTag
3389
+ ? document.createElementNS('http://www.w3.org/2000/svg', svgTag)
3390
+ : document.createElement(
3391
+ ['TextField', 'TextInput'].includes(type)
3392
+ ? 'input'
3393
+ : type === 'TextEdit'
3394
+ ? 'textarea'
3395
+ : type === 'Image'
3396
+ ? 'div'
3397
+ : type === 'Button'
3398
+ ? 'button'
3399
+ : 'div',
3400
+ );
3401
+ element.setAttribute('data-qml-node', String(index));
3402
+ element.setAttribute('data-qml-type', type);
3403
+ if (type === 'TextField') element.setAttribute('type', 'text');
3404
+ if (type === 'Button') element.setAttribute('type', 'button');
3405
+ const marker = elements.get(before);
3406
+ if (marker) {
3407
+ parent = parents.get(before);
3408
+ marker.parentElement.insertBefore(element, marker);
3409
+ } else (elements.get(parent) ?? document.body).appendChild(element);
3410
+ if (nonvisual.has(type))
3411
+ element.style.display = type === 'ShellRoot' ? 'contents' : 'none';
3412
+ const previous = disposers;
3413
+ if (persistent) disposers = null;
3414
+ try {
3415
+ return make(element, type, parent);
3416
+ } finally {
3417
+ disposers = previous;
3418
+ }
3419
+ };
3420
+ const textures = createQmlTextures({ elements, state, effect, untrack, signal, host,
3421
+ cleanup: fn => disposers?.push(fn) });
3422
+ const shapes = createQmlShapes({
3423
+ texture: textures.image,
3424
+ elements,
3425
+ parents,
3426
+ owners,
3427
+ cssColor,
3428
+ cleanup: (fn) => disposers?.push(fn),
3429
+ children: (value) => {
3430
+ const box = childSet(value);
3431
+ if (!box) return [];
3432
+ box.version.value;
3433
+ return [...box.items];
3434
+ },
3435
+ });
3436
+ const images = createQmlImages({
3437
+ document,
3438
+ effect,
3439
+ listen,
3440
+ host,
3441
+ cleanup: (fn) => disposers?.push(fn),
3442
+ });
3443
+ const shortcuts=createQmlShortcuts({emit,activeWindow:()=>activeWindow,cleanup:fn=>disposers?.push(fn)});
3444
+ const shaders=createQmlShaderEffects({effect,untrack,elements,host,texture:v=>textures.image(v),cleanup:fn=>disposers?.push(fn),resolve:(v,url)=>new URL(String(url),descriptions.get(v)?.url||document.baseURI).href});
3445
+ const canvases=createQmlCanvas({effect,untrack,emit,cssColor,cleanup:fn=>disposers?.push(fn)});
3446
+ const media=createQmlMedia({document,elements,effect,untrack,watchWrite,listen,host,emit,
3447
+ cleanup:fn=>disposers?.push(fn),resolve:(v,url)=>new URL(String(url),descriptions.get(v)?.url||document.baseURI).href});
3448
+ const models = createQmlModels({
3449
+ state,
3450
+ effect,
3451
+ untrack,
3452
+ instantiate,
3453
+ factories,
3454
+ owners,
3455
+ parents,
3456
+ elements,
3457
+ emit,
3458
+ listen,
3459
+ nodes,
3460
+ host,
3461
+ descriptions,
3462
+ scope: () => disposers,
3463
+ watchWrite,
3464
+ start,
3465
+ });
3466
+ return {
3467
+ // QML String.arg without changing frozen or shared JavaScript intrinsics.
3468
+ // https://doc.qt.io/qt-6/qml-string.html
3469
+ argFor(source) {
3470
+ if (typeof source !== 'string') return (...args) => source.arg(...args);
3471
+ return (value) => {
3472
+ const markers = [...source.matchAll(/%([1-9][0-9]?)/g)];
3473
+ if (!markers.length) return source;
3474
+ const lowest = Math.min(...markers.map((match) => Number(match[1])));
3475
+ return source.replace(/%([1-9][0-9]?)/g, (match, index) =>
3476
+ Number(index) === lowest ? String(value) : match,
3477
+ );
3478
+ };
3479
+ },
3480
+ create,
3481
+ console: Object.fromEntries(
3482
+ ['debug', 'log', 'info', 'warn', 'error'].map((name) => [
3483
+ name,
3484
+ (...args) => {
3485
+ if (owners.get(args[0])?.type === 'LoggingCategory') {
3486
+ const category = args.shift(),
3487
+ rank = { 0: 0, 4: 1, 1: 2, 2: 3, 3: 4 };
3488
+ if (
3489
+ { debug: 0, log: 0, info: 1, warn: 2, error: 3 }[name] <
3490
+ (rank[category.defaultLogLevel] ?? 0)
3491
+ )
3492
+ return;
3493
+ args.unshift(category.name + ':');
3494
+ }
3495
+ globalThis.console[name](...args);
3496
+ },
3497
+ ]),
3498
+ ),
3499
+ attachDelegate: (cell, index, view) =>
3500
+ models.attachDelegate(cell, index, view),
3501
+ ipc: {
3502
+ targets: () => [...ipcHandlers.keys()],
3503
+ call: (target, method, ...args) => {
3504
+ const handler = ipcHandlers.get(target);
3505
+ if (
3506
+ !handler ||
3507
+ typeof handler[method] !== 'function' ||
3508
+ ['constructor', '__proto__', 'prototype'].includes(method)
3509
+ )
3510
+ throw Error('Unknown QML IPC method');
3511
+ return handler[method](...args);
3512
+ },
3513
+ },
3514
+ behavior: (target, key, value, animation) => {
3515
+ let map = behaviors.get(target);
3516
+ if (!map) behaviors.set(target, (map = new Map()));
3517
+ map.set(key, { value, animation });
3518
+ value.targetProperty = { object: target, name: key };
3519
+ value.animation = animation;
3520
+ },
3521
+ dimension,
3522
+ position,
3523
+ qt,
3524
+ share: (url, value) => {
3525
+ if (!sharedValues.has(url)) sharedValues.set(url, value);
3526
+ },
3527
+ shared: (url) => sharedValues.get(url),
3528
+ apply: (value, properties) => {
3529
+ for (const [key, next] of Object.entries(properties)) {
3530
+ if (descriptions.get(value)?.readonly.includes(key))
3531
+ throw Error('Cannot override readonly QML property: ' + key);
3532
+ if (
3533
+ !(key in value) &&
3534
+ !descriptions.get(value)?.properties.includes(key)
3535
+ )
3536
+ throw Error('Unknown initial property: ' + key);
3537
+ (aliases.get(value)?.has(key) ? bindAlias : bind)(
3538
+ value,
3539
+ key,
3540
+ Qt.bindingValue(next) ?? (() => next),
3541
+ );
3542
+ }
3543
+ },
3544
+ describe: (value, tags, properties, url, readonly = []) => {
3545
+ descriptions.set(value, { tags, properties, url, readonly });
3546
+ for (const key of new Set([...Object.keys(value), ...properties])) {
3547
+ if (
3548
+ key.includes('.') ||
3549
+ key.endsWith('Changed') ||
3550
+ key + 'Changed' in value
3551
+ )
3552
+ continue;
3553
+ let changed;
3554
+ Object.defineProperty(value, key + 'Changed', {
3555
+ configurable: true,
3556
+ get: () => {
3557
+ if (changed) return changed;
3558
+ changed = signal();
3559
+ // QML property notify signals are synchronous: connected handlers
3560
+ // can mirror state which the caller reads immediately afterward.
3561
+ let previous = untrack(() => value[key]);
3562
+ const previousScope = disposers;
3563
+ disposers = owners.get(value)?.scope ?? null;
3564
+ try {
3565
+ effect(() => {
3566
+ const next = value[key];
3567
+ if (!Object.is(next, previous)) {
3568
+ previous = next;
3569
+ changed();
3570
+ }
3571
+ }, { sync: true });
3572
+ } finally {
3573
+ disposers = previousScope;
3574
+ }
3575
+ return changed;
3576
+ },
3577
+ });
3578
+ }
3579
+ },
3580
+ cast: (value, type) =>
3581
+ descriptions.get(value)?.tags.includes(type.split('.').at(-1))
3582
+ ? value
3583
+ : null,
3584
+ element: (value) => elements.get(value),
3585
+ listen: (value, name, fn) =>
3586
+ listen(elements.get(value), name, (event) => {
3587
+ if (enabled(value)) fn(event);
3588
+ }),
3589
+ delegate: (value, factory) => factories.set(value, factory),
3590
+ component: (value, factory) => factories.set(value, factory),
3591
+ make,
3592
+ bind,
3593
+ bindAlias,
3594
+ alias,
3595
+ viewport,
3596
+ signal,
3597
+ on,
3598
+ start,
3599
+ Qt,
3600
+ Quickshell,
3601
+ Hyprland,
3602
+ };
3603
+ }