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