buninu-browser 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/LICENSES/jsmdcui.txt +22 -0
  2. package/README.md +40 -4
  3. package/frontends/wayland/LICENSE-land +21 -0
  4. package/frontends/wayland/README.md +152 -0
  5. package/frontends/wayland/casty/LICENSE +21 -0
  6. package/frontends/wayland/casty/bookmarks.js +32 -0
  7. package/frontends/wayland/casty/click.js +96 -0
  8. package/frontends/wayland/casty/commands.js +61 -0
  9. package/frontends/wayland/casty/config.js +45 -0
  10. package/frontends/wayland/casty/download-directory.js +28 -0
  11. package/frontends/wayland/casty/download.js +49 -0
  12. package/frontends/wayland/casty/hints.js +334 -0
  13. package/frontends/wayland/casty/keys.js +56 -0
  14. package/frontends/wayland/casty/page-commands.js +80 -0
  15. package/frontends/wayland/casty/prompt-width.js +148 -0
  16. package/frontends/wayland/casty/urlbar.js +606 -0
  17. package/frontends/wayland/client.js +88 -0
  18. package/frontends/wayland/clipboard.js +80 -0
  19. package/frontends/wayland/index.js +130 -0
  20. package/frontends/wayland/input.js +75 -0
  21. package/frontends/wayland/protocol.js +55 -0
  22. package/frontends/wayland/protocols/wayland.xml +3248 -0
  23. package/frontends/wayland/protocols/xdg-shell.xml +1415 -0
  24. package/frontends/wayland/transport.js +120 -0
  25. package/frontends/wayland/urlbar.js +162 -0
  26. package/index.js +28 -1
  27. package/lib/audio/shared-connection.js +31 -0
  28. package/lib/cdp-server.js +15 -2
  29. package/lib/clipboard-command.js +10 -0
  30. package/lib/clipboard-powershell.js +22 -0
  31. package/lib/clipboard.js +37 -2
  32. package/lib/document-types.js +11 -0
  33. package/lib/headless-shell.js +54 -12
  34. package/lib/layout/text-layout.js +17 -9
  35. package/lib/paint/display-list.js +6 -1
  36. package/lib/qml/README.md +1427 -0
  37. package/lib/qml/builtins.js +771 -0
  38. package/lib/qml/canvas-dom.js +115 -0
  39. package/lib/qml/canvas.js +76 -0
  40. package/lib/qml/document.js +1199 -0
  41. package/lib/qml/fonts.js +69 -0
  42. package/lib/qml/format.js +9 -0
  43. package/lib/qml/host.js +352 -0
  44. package/lib/qml/images.js +199 -0
  45. package/lib/qml/launcher.js +132 -0
  46. package/lib/qml/loader.js +521 -0
  47. package/lib/qml/media-renderer.js +49 -0
  48. package/lib/qml/media.js +86 -0
  49. package/lib/qml/models.js +622 -0
  50. package/lib/qml/parser.js +381 -0
  51. package/lib/qml/qs +5 -0
  52. package/lib/qml/qscasty +3 -0
  53. package/lib/qml/qsheadless +3 -0
  54. package/lib/qml/qswayland +3 -0
  55. package/lib/qml/reactive.js +182 -0
  56. package/lib/qml/runtime.js +3603 -0
  57. package/lib/qml/shader/fragment.js +83 -0
  58. package/lib/qml/shader/qsb.js +27 -0
  59. package/lib/qml/shader/renderer.js +39 -0
  60. package/lib/qml/shader/runtime.js +61 -0
  61. package/lib/qml/shapes.js +251 -0
  62. package/lib/qml/shortcuts.js +65 -0
  63. package/lib/qml/text.js +109 -0
  64. package/lib/qml/textures.js +126 -0
  65. package/lib/qml/wayland-clipboard.js +32 -0
  66. package/lib/qml/window-constraints.js +16 -0
  67. package/lib/qml/worker-thread.js +70 -0
  68. package/lib/render-tree/index.js +16 -1
  69. package/lib/renderer/bindings.js +16 -1
  70. package/lib/renderer/canvas-2d.js +29 -0
  71. package/lib/renderer/document-selection.js +19 -0
  72. package/lib/renderer/host.js +45 -17
  73. package/lib/renderer/main.js +21 -6
  74. package/lib/renderer/page-realm.js +20 -10
  75. package/lib/renderer/page-renderer.js +497 -42
  76. package/lib/renderer/pointer-move.js +5 -3
  77. package/lib/renderer/render-interval.js +9 -0
  78. package/lib/renderer/text-input-paint.js +54 -0
  79. package/lib/renderer/text-input.js +57 -0
  80. package/lib/style/computed-style.js +10 -4
  81. package/package.json +1 -1
  82. package/test/audio-connection-lifetime.test.js +46 -0
  83. package/test/canvas-paint.test.js +8 -0
  84. package/test/clipboard.test.js +19 -0
  85. package/test/computed-style.test.js +12 -0
  86. package/test/document-selection.test.js +27 -0
  87. package/test/double-click.test.js +85 -0
  88. package/test/fixtures/canvas-damage.js +40 -0
  89. package/test/fixtures/fake-renderer.js +1 -0
  90. package/test/fixtures/flea-helpers/Dockerfile +36 -0
  91. package/test/fixtures/flea-helpers/bat.mjs +43 -0
  92. package/test/fixtures/flea-helpers/bin/bat +3 -0
  93. package/test/fixtures/flea-helpers/bin/bwrap +10 -0
  94. package/test/fixtures/flea-helpers/bin/bwrap-real-filter +9 -0
  95. package/test/fixtures/flea-helpers/bin/glow +3 -0
  96. package/test/fixtures/flea-helpers/bin/qs +3 -0
  97. package/test/fixtures/flea-helpers/bin/unsupported +3 -0
  98. package/test/fixtures/flea-helpers/check-highlighting.mjs +47 -0
  99. package/test/fixtures/flea-helpers/check-media.mjs +29 -0
  100. package/test/fixtures/flea-helpers/check-preview.mjs +27 -0
  101. package/test/fixtures/flea-helpers/check.mjs +36 -0
  102. package/test/fixtures/flea-helpers/highlight-cases.json +227 -0
  103. package/test/fixtures/flea-helpers/source-mime.xml +28 -0
  104. package/test/fixtures/flea-helpers/syntax-compat.mjs +23 -0
  105. package/test/fixtures/flea-thumbnail-stub/flea-placeholder.thumbnailer +4 -0
  106. package/test/fixtures/flea-thumbnail-stub/flea-thumbnail-stub +3 -0
  107. package/test/fixtures/omastorm-helpers/Dockerfile +35 -0
  108. package/test/fixtures/omastorm-helpers/check.mjs +56 -0
  109. package/test/fixtures/omastorm-helpers/launch.mjs +24 -0
  110. package/test/fixtures/omastorm-helpers/prepare.mjs +17 -0
  111. package/test/fixtures/omastorm-helpers/runtime.mjs +32 -0
  112. package/test/fixtures/pointer-paint-batching.js +23 -0
  113. package/test/fixtures/qml/flea/EscapeHint.qml +11 -0
  114. package/test/fixtures/qml/flea/LICENSE +21 -0
  115. package/test/fixtures/qml/flea/README.md +25 -0
  116. package/test/fixtures/qml/flea/Theme.qml +8 -0
  117. package/test/fixtures/qml/flea/demo.qml +24 -0
  118. package/test/fixtures/qml/flea/qmldir +3 -0
  119. package/test/fixtures/qml/flea-theme/LICENSE-Flea +21 -0
  120. package/test/fixtures/qml/flea-theme/LICENSE-Omarchy +20 -0
  121. package/test/fixtures/qml/flea-theme/README.md +70 -0
  122. package/test/fixtures/qml/flea-theme/button.qml +17 -0
  123. package/test/fixtures/qml/flea-theme/demo.qml +27 -0
  124. package/test/fixtures/qml/flea-theme/field.qml +21 -0
  125. package/test/fixtures/qml/flea-theme/imports/qs/Commons/BorderGeometry.js +373 -0
  126. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Color.qml +258 -0
  127. package/test/fixtures/qml/flea-theme/imports/qs/Commons/I18n.qml +118 -0
  128. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Numbers.js +19 -0
  129. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Plural.js +91 -0
  130. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Style.qml +561 -0
  131. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Util.qml +188 -0
  132. package/test/fixtures/qml/flea-theme/imports/qs/Commons/qmldir +13 -0
  133. package/test/fixtures/qml/flea-theme/segment.qml +17 -0
  134. package/test/fixtures/qml/flea-theme/settings.qml +58 -0
  135. package/test/fixtures/qml/flea-theme/text-metrics.qml +10 -0
  136. package/test/fixtures/qml/flea-theme/ui/DialogButton.qml +150 -0
  137. package/test/fixtures/qml/flea-theme/ui/DialogField.qml +147 -0
  138. package/test/fixtures/qml/flea-theme/ui/EscapeHint.qml +11 -0
  139. package/test/fixtures/qml/flea-theme/ui/Glass.qml +114 -0
  140. package/test/fixtures/qml/flea-theme/ui/Glyph.qml +54 -0
  141. package/test/fixtures/qml/flea-theme/ui/GlyphSolid.qml +22 -0
  142. package/test/fixtures/qml/flea-theme/ui/Reveal.qml +54 -0
  143. package/test/fixtures/qml/flea-theme/ui/SettingsRuler.qml +85 -0
  144. package/test/fixtures/qml/flea-theme/ui/SettingsSegment.qml +85 -0
  145. package/test/fixtures/qml/flea-theme/ui/StableModel.qml +89 -0
  146. package/test/fixtures/qml/flea-theme/ui/Theme.qml +460 -0
  147. package/test/fixtures/qml/flea-theme/ui/ThemeColors.qml +43 -0
  148. package/test/fixtures/qml/flea-theme/ui/ViewState.qml +565 -0
  149. package/test/fixtures/qml/flea-theme/ui/js/Anchor.js +379 -0
  150. package/test/fixtures/qml/flea-theme/ui/js/AnchorHold.js +162 -0
  151. package/test/fixtures/qml/flea-theme/ui/js/Archive.js +157 -0
  152. package/test/fixtures/qml/flea-theme/ui/js/BackReturn.js +110 -0
  153. package/test/fixtures/qml/flea-theme/ui/js/Buttons.js +54 -0
  154. package/test/fixtures/qml/flea-theme/ui/js/ColumnMenu.js +111 -0
  155. package/test/fixtures/qml/flea-theme/ui/js/Columns.js +298 -0
  156. package/test/fixtures/qml/flea-theme/ui/js/Contrast.js +157 -0
  157. package/test/fixtures/qml/flea-theme/ui/js/Density.js +44 -0
  158. package/test/fixtures/qml/flea-theme/ui/js/DirSizes.js +129 -0
  159. package/test/fixtures/qml/flea-theme/ui/js/ExtThumbs.js +115 -0
  160. package/test/fixtures/qml/flea-theme/ui/js/Filter.js +284 -0
  161. package/test/fixtures/qml/flea-theme/ui/js/FolderSorts.js +108 -0
  162. package/test/fixtures/qml/flea-theme/ui/js/Format.js +305 -0
  163. package/test/fixtures/qml/flea-theme/ui/js/Icons.js +206 -0
  164. package/test/fixtures/qml/flea-theme/ui/js/Input.js +39 -0
  165. package/test/fixtures/qml/flea-theme/ui/js/KeyName.js +64 -0
  166. package/test/fixtures/qml/flea-theme/ui/js/Keymap.js +370 -0
  167. package/test/fixtures/qml/flea-theme/ui/js/Kinds.js +145 -0
  168. package/test/fixtures/qml/flea-theme/ui/js/Lists.js +11 -0
  169. package/test/fixtures/qml/flea-theme/ui/js/MakeDefault.js +186 -0
  170. package/test/fixtures/qml/flea-theme/ui/js/Match.js +27 -0
  171. package/test/fixtures/qml/flea-theme/ui/js/Material.js +109 -0
  172. package/test/fixtures/qml/flea-theme/ui/js/Menu.js +513 -0
  173. package/test/fixtures/qml/flea-theme/ui/js/Motion.js +16 -0
  174. package/test/fixtures/qml/flea-theme/ui/js/Mounts.js +320 -0
  175. package/test/fixtures/qml/flea-theme/ui/js/MouseNav.js +13 -0
  176. package/test/fixtures/qml/flea-theme/ui/js/Nav.js +328 -0
  177. package/test/fixtures/qml/flea-theme/ui/js/Palette.js +33 -0
  178. package/test/fixtures/qml/flea-theme/ui/js/Places.js +236 -0
  179. package/test/fixtures/qml/flea-theme/ui/js/Protocols.js +296 -0
  180. package/test/fixtures/qml/flea-theme/ui/js/RecentMode.js +154 -0
  181. package/test/fixtures/qml/flea-theme/ui/js/Search.js +396 -0
  182. package/test/fixtures/qml/flea-theme/ui/js/Settings.js +476 -0
  183. package/test/fixtures/qml/flea-theme/ui/js/SettingsAbout.js +99 -0
  184. package/test/fixtures/qml/flea-theme/ui/js/SettingsShelf.js +52 -0
  185. package/test/fixtures/qml/flea-theme/ui/js/Sort.js +127 -0
  186. package/test/fixtures/qml/flea-theme/ui/js/StateWords.js +42 -0
  187. package/test/fixtures/qml/flea-theme/ui/js/Swap.js +166 -0
  188. package/test/fixtures/qml/flea-theme/ui/js/TextSize.js +107 -0
  189. package/test/fixtures/qml/flea-theme/ui/js/Thumbs.js +158 -0
  190. package/test/fixtures/qml/flea-theme/ui/js/UiState.js +438 -0
  191. package/test/fixtures/qml/flea-theme/ui/js/Update.js +132 -0
  192. package/test/fixtures/qml/flea-theme/ui/js/UserDirs.js +56 -0
  193. package/test/fixtures/qml/flea-theme/ui/qmldir +227 -0
  194. package/test/fixtures/qml/hello.qml +26 -0
  195. package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabel.qml +2 -0
  196. package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabelNew.qml +2 -0
  197. package/test/fixtures/qml/imports/Demo/Toolkit/qmldir +3 -0
  198. package/test/fixtures/qml/imports/components/Badge.qml +15 -0
  199. package/test/fixtures/qml/imports/logic/base.js +2 -0
  200. package/test/fixtures/qml/imports/logic/counter.js +4 -0
  201. package/test/fixtures/qml/imports/logic/math.js +3 -0
  202. package/test/fixtures/qml/imports/logic/shared.js +3 -0
  203. package/test/fixtures/qml/imports/main.qml +21 -0
  204. package/test/fixtures/qml/imports/theme/Theme.qml +8 -0
  205. package/test/fixtures/qml/imports/theme/qmldir +2 -0
  206. package/test/fixtures/qml/multiply.qml +61 -0
  207. package/test/fixtures/qml-canvas-dom.js +90 -0
  208. package/test/fixtures/qml-fonts/main.qml +10 -0
  209. package/test/fixtures/qml-locale/check.mjs +6 -0
  210. package/test/fixtures/qml-locale/main.qml +8 -0
  211. package/test/fixtures/qml-media-audio.js +39 -0
  212. package/test/fixtures/qml-shader/grab.qml +14 -0
  213. package/test/fixtures/qml-shader/gradient.frag +5 -0
  214. package/test/fixtures/qml-shader/main.qml +14 -0
  215. package/test/fixtures/qml-shader/texture-grab.qml +25 -0
  216. package/test/fixtures/qml-shader/texture-size.frag +5 -0
  217. package/test/fixtures/qml-shader-check.mjs +24 -0
  218. package/test/fixtures/title-no-paint.js +20 -0
  219. package/test/helpers/flea-sandbox.js +48 -0
  220. package/test/helpers/quickshell-fixture.js +125 -0
  221. package/test/html-clipboard-wayland.test.js +47 -0
  222. package/test/index.test.js +13 -0
  223. package/test/manual-casty-without-qml.js +87 -0
  224. package/test/manual-double-click.html +21 -0
  225. package/test/manual-flea-application.js +49 -0
  226. package/test/manual-flea-binary.js +62 -0
  227. package/test/manual-flea-qml.js +9 -0
  228. package/test/manual-flea-settings.js +40 -0
  229. package/test/manual-flea-theme.js +40 -0
  230. package/test/manual-mouse-buttons.html +18 -0
  231. package/test/manual-omastorm-engine.mjs +19 -0
  232. package/test/manual-omastorm-shaders.mjs +28 -0
  233. package/test/manual-omastorm-software.mjs +25 -0
  234. package/test/manual-pointer-scroll.html +43 -0
  235. package/test/manual-qml-removal.js +46 -0
  236. package/test/manual-without-qml.js +45 -0
  237. package/test/pointer-drag.test.js +104 -0
  238. package/test/qml-canvas-dom.test.js +37 -0
  239. package/test/qml-canvas.test.js +21 -0
  240. package/test/qml-clipboard-bridge.test.js +25 -0
  241. package/test/qml-clipboard-wayland.test.js +40 -0
  242. package/test/qml-connections-timing.test.js +95 -0
  243. package/test/qml-controls-window.test.js +182 -0
  244. package/test/qml-dynamic.test.js +401 -0
  245. package/test/qml-flea-app.test.js +146 -0
  246. package/test/qml-flea-backend.test.js +78 -0
  247. package/test/qml-flea-columns-backend.test.js +38 -0
  248. package/test/qml-flea-compile.test.js +78 -0
  249. package/test/qml-flea-launcher.test.js +88 -0
  250. package/test/qml-flea-markdown-backend.test.js +31 -0
  251. package/test/qml-flea-media.test.js +28 -0
  252. package/test/qml-flea-navigation-backend.test.js +35 -0
  253. package/test/qml-flea-navigation-timing.test.js +147 -0
  254. package/test/qml-flea-preview-backend.test.js +53 -0
  255. package/test/qml-flea-previews.test.js +163 -0
  256. package/test/qml-flea-rows.test.js +101 -0
  257. package/test/qml-flea-textures.test.js +59 -0
  258. package/test/qml-flea-view-switch.test.js +33 -0
  259. package/test/qml-flea-wayland.test.js +68 -0
  260. package/test/qml-flea.test.js +34 -0
  261. package/test/qml-fonts.test.js +31 -0
  262. package/test/qml-grid-multiline.test.js +48 -0
  263. package/test/qml-host.test.js +238 -0
  264. package/test/qml-imports.test.js +102 -0
  265. package/test/qml-inherited-handlers.test.js +51 -0
  266. package/test/qml-input-selection.test.js +66 -0
  267. package/test/qml-interactions.test.js +309 -0
  268. package/test/qml-language.test.js +167 -0
  269. package/test/qml-launcher.test.js +54 -0
  270. package/test/qml-layout-text.test.js +205 -0
  271. package/test/qml-locale.test.js +45 -0
  272. package/test/qml-media.test.js +63 -0
  273. package/test/qml-nonvisual-visible.test.js +19 -0
  274. package/test/qml-persisted-delegates.test.js +45 -0
  275. package/test/qml-quickshell-controls.test.js +65 -0
  276. package/test/qml-quickshell-settings.test.js +62 -0
  277. package/test/qml-repeater.test.js +72 -0
  278. package/test/qml-shader-runtime.test.js +6 -0
  279. package/test/qml-shortcuts.test.js +39 -0
  280. package/test/qml-socket.test.js +60 -0
  281. package/test/qml-software-shader.test.js +55 -0
  282. package/test/qml-style-updates.test.js +124 -0
  283. package/test/qml-textures.test.js +66 -0
  284. package/test/qml-views.test.js +332 -0
  285. package/test/qml-window-update.test.js +36 -0
  286. package/test/qml-worker.test.js +69 -0
  287. package/test/qml.test.js +83 -0
  288. package/test/render-interval.test.js +33 -0
  289. package/test/renderer-host.test.js +11 -0
  290. package/test/text-input-pointer.test.js +31 -0
  291. package/test/text-input-scroll.test.js +29 -0
  292. package/test/text-input.test.js +47 -0
  293. package/test/title-no-paint.test.js +6 -0
  294. package/test/wayland-frontend.test.js +83 -0
  295. package/test/wayland-scroll.test.js +40 -0
  296. package/test/wayland-urlbar.test.js +61 -0
@@ -0,0 +1,622 @@
1
+ // Serialized with the QML runtime. Models keep data/ranges; views instantiate only
2
+ // the visible delegate factories. No HTML strings or retained QML syntax trees.
3
+ export function createQmlModels(api) {
4
+ const {
5
+ state,
6
+ effect,
7
+ untrack,
8
+ instantiate,
9
+ factories,
10
+ owners,
11
+ parents,
12
+ elements,
13
+ emit,
14
+ listen,
15
+ nodes,
16
+ scope,
17
+ host,
18
+ descriptions,
19
+ watchWrite,
20
+ } = api;
21
+ const xmlModels = new WeakMap(),
22
+ lists = new WeakMap(),
23
+ groups = new WeakMap(),
24
+ views = new WeakMap(),
25
+ delegates = new WeakMap();
26
+ let creating = null;
27
+ const attachDelegate = (cell, index, view) => {
28
+ if (!['ListView', 'GridView'].includes(owners.get(view)?.type)) return;
29
+ const model = view.model,
30
+ info = delegates.get(model);
31
+ const attached = state({
32
+ inPersistedItems:
33
+ !!creating && creating.model === model && creating.index === index,
34
+ });
35
+ cell.DelegateModel = attached;
36
+ if (info) {
37
+ const forget = () => {
38
+ for (const [at, item] of info.held)
39
+ if (item === cell) info.held.delete(at);
40
+ info.persisted.count = info.held.size;
41
+ };
42
+ watchWrite(attached, 'inPersistedItems', () => {
43
+ if (attached.inPersistedItems) info.held.set(cell.index, cell);
44
+ else {
45
+ forget();
46
+ // A caller may adopt the item into its view in this same JS turn.
47
+ queueMicrotask(() => {
48
+ if (
49
+ !attached.inPersistedItems &&
50
+ ![...(views.get(view)?.instances.values() ?? [])].includes(cell)
51
+ )
52
+ cell.destroy?.();
53
+ });
54
+ }
55
+ info.persisted.count = info.held.size;
56
+ });
57
+ if (attached.inPersistedItems) {
58
+ info.held.set(index, cell);
59
+ info.persisted.count = info.held.size;
60
+ }
61
+ scope()?.push(forget);
62
+ }
63
+ };
64
+ const count = (model) =>
65
+ typeof model === 'number'
66
+ ? Math.max(0, Math.floor(model))
67
+ : Array.isArray(model)
68
+ ? model.length
69
+ : (model?.count ?? 0);
70
+ const get = (model, index) =>
71
+ typeof model === 'number'
72
+ ? index
73
+ : Array.isArray(model)
74
+ ? model[index]
75
+ : (model?.get?.(index) ?? index);
76
+ const groupCount = (ranges) => ranges.reduce((sum, [a, b]) => sum + b - a, 0);
77
+ const member = (ranges, index) => {
78
+ for (const [a, b] of ranges) {
79
+ if (index < b - a) return a + index;
80
+ index -= b - a;
81
+ }
82
+ return -1;
83
+ };
84
+ const add = (ranges, start, end) => {
85
+ const result = [];
86
+ for (const [a, b] of [...ranges, [start, end]].sort(
87
+ (a, b) => a[0] - b[0],
88
+ )) {
89
+ if (b <= a) continue;
90
+ const last = result.at(-1);
91
+ if (last && a <= last[1]) last[1] = Math.max(last[1], b);
92
+ else result.push([a, b]);
93
+ }
94
+ return result;
95
+ };
96
+ const remove = (ranges, start, end) =>
97
+ ranges.flatMap(([a, b]) =>
98
+ b <= start || a >= end
99
+ ? [[a, b]]
100
+ : [
101
+ [a, Math.min(b, start)],
102
+ [Math.max(a, end), b],
103
+ ].filter(([a, b]) => b > a),
104
+ );
105
+ function make(v, type) {
106
+ if (type === 'XmlListModel') {
107
+ Object.assign(v, {
108
+ source: '',
109
+ xml: '',
110
+ query: '',
111
+ status: 0,
112
+ progress: 0,
113
+ count: 0,
114
+ });
115
+ const data = state({ rows: [], error: '' });
116
+ xmlModels.set(v, data);
117
+ v.get = (i) => data.rows[i];
118
+ v.index = (row, column = 0) => ({ row, column });
119
+ v.data = (index, role) =>
120
+ data.rows[index.row]?.[
121
+ nodes.filter(
122
+ (n) => parents.get(n.value) === v && n.type === 'XmlListModelRole',
123
+ )[role - 256]?.value.name
124
+ ];
125
+ v.errorString = () => data.error;
126
+ }
127
+ if (type === 'XmlListModelRole')
128
+ Object.assign(v, { name: '', elementName: '', attributeName: '' });
129
+ if (type === 'ListModel') {
130
+ const data = state({ rows: [], version: 0 });
131
+ lists.set(v, data);
132
+ v.dynamicRoles = false;
133
+ Object.defineProperty(v, 'count', { get: () => data.rows.length });
134
+ v.get = (index) => {
135
+ data.version;
136
+ return data.rows[index];
137
+ };
138
+ v.append = (rows) => {
139
+ data.rows = [...data.rows, ...(Array.isArray(rows) ? rows : [rows])];
140
+ };
141
+ v.insert = (index, rows) => {
142
+ const copy = [...data.rows];
143
+ copy.splice(index, 0, ...(Array.isArray(rows) ? rows : [rows]));
144
+ data.rows = copy;
145
+ };
146
+ v.remove = (index, n = 1) => {
147
+ const copy = [...data.rows];
148
+ copy.splice(index, n);
149
+ data.rows = copy;
150
+ };
151
+ v.clear = () => {
152
+ data.rows = [];
153
+ };
154
+ v.set = (index, row) => {
155
+ const copy = [...data.rows];
156
+ copy[index] = { ...copy[index], ...row };
157
+ data.rows = copy;
158
+ };
159
+ v.setProperty = (index, key, value) => v.set(index, { [key]: value });
160
+ v.move = (from, to, n) => {
161
+ const copy = [...data.rows],
162
+ part = copy.splice(from, n);
163
+ copy.splice(to, 0, ...part);
164
+ data.rows = copy;
165
+ };
166
+ }
167
+ if (type === 'DelegateModelGroup') {
168
+ Object.assign(v, { name: '', includeByDefault: false });
169
+ const data = state({ ranges: [] });
170
+ groups.set(v, data);
171
+ Object.defineProperty(v, 'count', { get: () => groupCount(data.ranges) });
172
+ v.get = (index) => ({ itemsIndex: member(data.ranges, index) });
173
+ v.removeGroups = (from, n, names) => {
174
+ const namesList = Array.isArray(names) ? names : [names];
175
+ if (!namesList.includes(v.name)) return;
176
+ let left = n,
177
+ position = from,
178
+ next = data.ranges;
179
+ const cuts = [];
180
+ for (const [a, b] of data.ranges) {
181
+ if (position >= b - a) {
182
+ position -= b - a;
183
+ continue;
184
+ }
185
+ const take = Math.min(left, b - a - position);
186
+ cuts.push([a + position, a + position + take]);
187
+ left -= take;
188
+ position = 0;
189
+ if (left <= 0) break;
190
+ }
191
+ for (const [a, b] of cuts) next = remove(next, a, b);
192
+ data.ranges = next;
193
+ };
194
+ }
195
+ if (type === 'DelegateModel') {
196
+ Object.assign(v, {
197
+ model: 0,
198
+ delegate: null,
199
+ groups: null,
200
+ filterOnGroup: 'items',
201
+ });
202
+ const persisted = state({ name: 'persistedItems', count: 0 }),
203
+ held = new Map();
204
+ delegates.set(v, { held, persisted });
205
+ const group = (name) =>
206
+ name === 'persistedItems'
207
+ ? persisted
208
+ : (Array.isArray(v.groups) ? v.groups : [v.groups]).find(
209
+ (g) => g?.name === name,
210
+ );
211
+ persisted.get = (index) => ({
212
+ itemsIndex: [...held.keys()].sort((a, b) => a - b)[index],
213
+ });
214
+ persisted.removeGroups = (from, n) => {
215
+ for (const index of [...held.keys()]
216
+ .sort((a, b) => a - b)
217
+ .slice(from, from + n)) {
218
+ const cell = held.get(index);
219
+ if (cell) cell.DelegateModel.inPersistedItems = false;
220
+ if (views.get(v._view)?.instances.get(index) !== cell)
221
+ cell?.destroy?.();
222
+ held.delete(index);
223
+ }
224
+ persisted.count = held.size;
225
+ };
226
+ v.persistedItems = persisted;
227
+ v.items = {
228
+ get: (index) => ({ itemsIndex: index, model: get(v.model, index) }),
229
+ addGroups: (from, n, names) => {
230
+ for (const name of Array.isArray(names) ? names : [names]) {
231
+ const target = group(name),
232
+ data = groups.get(target);
233
+ if (data)
234
+ data.ranges = add(
235
+ data.ranges,
236
+ Math.max(0, from),
237
+ Math.min(count(v.model), from + n),
238
+ );
239
+ }
240
+ },
241
+ create: (index) => {
242
+ if (!Number.isInteger(index) || index < 0 || index >= count(v.model))
243
+ return null;
244
+ const view = v._view,
245
+ factory = view && factories.get(view);
246
+ if (!factory) return null;
247
+ let cell = held.get(index) || views.get(view)?.instances.get(index);
248
+ if (cell) {
249
+ cell.DelegateModel.inPersistedItems = true;
250
+ return cell;
251
+ }
252
+ const previous = creating;
253
+ creating = { model: v, index };
254
+ try {
255
+ cell = instantiate(() => factory(index, get(v.model, index)), view);
256
+ } finally {
257
+ creating = previous;
258
+ }
259
+ return cell;
260
+ },
261
+ };
262
+ Object.defineProperty(v, 'count', {
263
+ get: () =>
264
+ v.filterOnGroup === 'items'
265
+ ? count(v.model)
266
+ : (group(v.filterOnGroup)?.count ?? 0),
267
+ });
268
+ v.get = (index) =>
269
+ get(
270
+ v.model,
271
+ v.filterOnGroup === 'items'
272
+ ? index
273
+ : member(groups.get(group(v.filterOnGroup))?.ranges ?? [], index),
274
+ );
275
+ }
276
+ if (type === 'ListView' || type === 'GridView')
277
+ Object.assign(v, {
278
+ header: null,
279
+ footer: null,
280
+ headerItem: null,
281
+ footerItem: null,
282
+ model: [],
283
+ count: 0,
284
+ currentIndex: -1,
285
+ currentItem: null,
286
+ cacheBuffer: 0,
287
+ displayMarginBeginning: 0,
288
+ displayMarginEnd: 0,
289
+ reuseItems: false,
290
+ keyNavigationEnabled: true,
291
+ keyNavigationWraps: false,
292
+ highlightMoveDuration: 0,
293
+ orientation: 2,
294
+ cellWidth: 100,
295
+ cellHeight: 100,
296
+ flow: 0,
297
+ });
298
+ }
299
+ function start(v, type) {
300
+ if (type === 'XmlListModel') {
301
+ const model = xmlModels.get(v);
302
+ let generation = 0,
303
+ disposed = false;
304
+ const roles = () =>
305
+ nodes
306
+ .filter(
307
+ (n) => parents.get(n.value) === v && n.type === 'XmlListModelRole',
308
+ )
309
+ .map((n) => n.value);
310
+ const path = (base, query) => {
311
+ if (!/^(?:\/?[A-Za-z_][\w:.-]*)+$/.test(query))
312
+ throw Error(
313
+ 'XmlListModel supports simple element paths only: ' + query,
314
+ );
315
+ let result = [base];
316
+ for (const name of query.split('/').filter(Boolean))
317
+ result = result.flatMap((n) =>
318
+ [...n.children].filter((child) => child.tagName === name),
319
+ );
320
+ return result;
321
+ };
322
+ v.reload = async () => {
323
+ const current = ++generation,
324
+ source = v.source,
325
+ xml = v.xml,
326
+ query = v.query;
327
+ model.rows = [];
328
+ v.count = 0;
329
+ v.progress = 0;
330
+ model.error = '';
331
+ if (!source && !xml) {
332
+ v.status = 0;
333
+ return;
334
+ }
335
+ v.status = 2;
336
+ try {
337
+ let text = xml;
338
+ if (!text) {
339
+ const url = new URL(source, descriptions.get(v).url).href;
340
+ if (url.startsWith('file:')) {
341
+ if (!host) throw Error('Local XML needs QML host');
342
+ const result = host.read(url);
343
+ if (result.error) throw Error(result.error);
344
+ text = result.text;
345
+ } else {
346
+ const response = await fetch(url);
347
+ if (!response.ok) throw Error('XML HTTP ' + response.status);
348
+ text = await response.text();
349
+ }
350
+ }
351
+ if (text.length > 4 * 1024 * 1024)
352
+ throw Error('XML model source limit exceeded');
353
+ const document = new DOMParser().parseFromString(
354
+ text,
355
+ 'application/xml',
356
+ );
357
+ if (document.querySelector('parsererror')) throw Error('Invalid XML');
358
+ const selected = path(document, query),
359
+ fields = roles();
360
+ if (selected.length > 100000)
361
+ throw Error('XML model row limit exceeded');
362
+ const next = selected.map((node) =>
363
+ Object.fromEntries(
364
+ fields.map((role) => {
365
+ const element = role.elementName
366
+ ? path(node, role.elementName)[0]
367
+ : node;
368
+ return [
369
+ role.name,
370
+ role.attributeName
371
+ ? (element?.getAttribute(role.attributeName) ?? '')
372
+ : (element?.textContent ?? ''),
373
+ ];
374
+ }),
375
+ ),
376
+ );
377
+ if (disposed || current !== generation) return;
378
+ model.rows = next;
379
+ v.count = next.length;
380
+ v.progress = 1;
381
+ v.status = 1;
382
+ } catch (e) {
383
+ if (disposed || current !== generation) return;
384
+ model.error = String(e.message);
385
+ v.status = 3;
386
+ }
387
+ };
388
+ effect(() => {
389
+ v.source;
390
+ v.xml;
391
+ v.query;
392
+ untrack(() => {
393
+ void v.reload();
394
+ });
395
+ });
396
+ scope()?.push(() => {
397
+ disposed = true;
398
+ generation++;
399
+ });
400
+ }
401
+
402
+ if (type === 'DelegateModelGroup') {
403
+ const parent = parents.get(v),
404
+ data = groups.get(v);
405
+ if (v.includeByDefault && parent?.model)
406
+ data.ranges = [[0, count(parent.model)]];
407
+ }
408
+ if (type !== 'ListView' && type !== 'GridView') return;
409
+ const instances = new Map(),
410
+ pool = [];
411
+ const measuredSizes = new Map(), revision = state({ value: 0 });
412
+ let measuredIndices = [], corrections = [], cacheWidth, cacheHeight, cacheHorizontal,
413
+ queued = false, disposed = false;
414
+ const schedule = () => {
415
+ if (queued || disposed) return;
416
+ queued = true;
417
+ queueMicrotask(() => { queued = false; if (!disposed) revision.value++; });
418
+ };
419
+ let busy = false,
420
+ rowSize = 24,
421
+ lastModel;
422
+ const horizontal = () => type === 'ListView' && v.orientation === 1;
423
+ const step = () =>
424
+ type === 'GridView' ? Math.max(1, v.cellHeight) : rowSize + v.spacing;
425
+ const cols = () =>
426
+ type === 'GridView'
427
+ ? Math.max(1, Math.floor(v.width / Math.max(1, v.cellWidth)))
428
+ : 1;
429
+ const clear = () => {
430
+ for (const cell of [...instances.values(), ...pool])
431
+ if (!cell.DelegateModel?.inPersistedItems) cell.destroy?.();
432
+ instances.clear();
433
+ pool.length = 0;
434
+ };
435
+ scope()?.push(() => { disposed = true; clear(); });
436
+ // Qt drains the pool at the write, even when it is re-enabled in the same
437
+ // JS turn (Flea StableModel.drainHidden). A batched effect misses that edge.
438
+ watchWrite(v, 'reuseItems', () => {
439
+ if (!v.reuseItems)
440
+ untrack(() => {
441
+ while (pool.length) pool.pop().destroy?.();
442
+ });
443
+ });
444
+ const rebuildSizes = () => {
445
+ measuredIndices = [...measuredSizes.keys()].sort((a, b) => a - b);
446
+ let sum = 0;
447
+ corrections = measuredIndices.map(i => {
448
+ sum += Math.max(1, measuredSizes.get(i) + v.spacing) - Math.max(1, step());
449
+ return sum;
450
+ });
451
+ };
452
+ const position = (index) => {
453
+ if (type === 'GridView') return Math.floor(index / cols()) * step();
454
+ let lo = 0, hi = measuredIndices.length;
455
+ while (lo < hi) { const mid = (lo + hi) >>> 1; if (measuredIndices[mid] < index) lo = mid + 1; else hi = mid; }
456
+ return index * Math.max(1, step()) + (lo ? corrections[lo - 1] : 0);
457
+ };
458
+ const indexAtOffset = (offset, total = v.count) => {
459
+ let lo = 0, hi = total;
460
+ while (lo < hi) { const mid = (lo + hi) >>> 1; if (position(mid + 1) <= offset) lo = mid + 1; else hi = mid; }
461
+ return lo;
462
+ };
463
+ const layout = () => {
464
+ revision.value;
465
+ const model = v.model,
466
+ total = count(model),
467
+ width = v.width,
468
+ height = v.height,
469
+ buffer = v.cacheBuffer,
470
+ marginStart = v.displayMarginBeginning,
471
+ marginEnd = v.displayMarginEnd,
472
+ reuse = v.reuseItems,
473
+ current = v.currentIndex;
474
+ let offset = horizontal() ? v.contentX : v.contentY;
475
+ // Read every instantiated delegate dimension reactively. Offscreen sizes
476
+ // stay as sparse measurements; unseen rows retain an estimate, without
477
+ // allocating or iterating over the whole model.
478
+ const measurements = type === 'ListView' ? [...instances].map(([i, cell]) =>
479
+ [i, horizontal() ? cell.width : cell.height]) : [];
480
+ // A synchronous bound-property read can re-enter layout. Keep tracking
481
+ // its inputs before returning, or that effect loses future updates.
482
+ if (busy) return;
483
+ // Model replacement starts at the origin; mutations of the same model
484
+ // retain the current position. Numeric models are replacements too.
485
+ if (lastModel !== undefined && lastModel !== model) {
486
+ untrack(() => { v.contentX = v.originX; v.contentY = v.originY; });
487
+ offset = horizontal() ? v.originX : v.originY;
488
+ }
489
+ if (type === 'ListView') {
490
+ if (lastModel !== model || cacheWidth !== width || cacheHorizontal !== horizontal() || (horizontal() && cacheHeight !== height)) measuredSizes.clear();
491
+ cacheWidth = width; cacheHeight = height; cacheHorizontal = horizontal();
492
+ if (lastModel === model) for (const [i, size] of measurements)
493
+ if (i < total && Number.isFinite(size) && size >= 0) measuredSizes.set(i, size);
494
+ for (const i of measuredSizes.keys()) if (i >= total) measuredSizes.delete(i);
495
+ if (measuredSizes.get(0) > 0) rowSize = measuredSizes.get(0);
496
+ rebuildSizes();
497
+ }
498
+ const size = Math.max(1, step()),
499
+ columns = cols();
500
+ const beginning = Math.max(0, offset - buffer - marginStart),
501
+ ending = offset + (horizontal() ? width : height) + buffer + marginEnd;
502
+ const first = type === 'ListView' ? indexAtOffset(beginning, total) : Math.floor(beginning / size) * columns,
503
+ last = Math.min(total, type === 'ListView' ? indexAtOffset(ending, total) + 2 : Math.ceil(ending / size) * columns + columns);
504
+ if (last - first > 500)
505
+ throw Error('QML view visible delegate limit (500)');
506
+ busy = true;
507
+ try {
508
+ untrack(() => {
509
+ if (lastModel !== model) {
510
+ clear();
511
+ lastModel = model;
512
+ }
513
+ if (model && typeof model === 'object' && !Array.isArray(model))
514
+ model._view = v;
515
+ v.count = total;
516
+ if (!reuse) while (pool.length) pool.pop().destroy?.();
517
+ for (const [index, cell] of instances)
518
+ if (index < first || index >= last) {
519
+ instances.delete(index);
520
+ schedule();
521
+ if (cell.DelegateModel?.inPersistedItems) continue;
522
+ if (reuse) {
523
+ cell.visible = false;
524
+ emit(cell, type + '.onPooled');
525
+ pool.push(cell);
526
+ } else if (!cell.DelegateModel?.inPersistedItems)
527
+ cell.destroy?.();
528
+ }
529
+ for (let index = first; index < last; index++) {
530
+ let cell = instances.get(index);
531
+ const data = get(model, index);
532
+ if (!cell) {
533
+ const held = delegates.get(model)?.held.get(index);
534
+ cell = held || pool.pop();
535
+ if (cell) {
536
+ cell.index = index;
537
+ cell.modelData = data;
538
+ cell.visible = true;
539
+ if (!held) emit(cell, type + '.onReused');
540
+ } else cell = instantiate(() => factories.get(v)(index, data), v);
541
+ instances.set(index, cell);
542
+ schedule();
543
+ } else if (cell.modelData !== data) cell.modelData = data;
544
+ if (type === 'GridView') {
545
+ cell.x = (index % columns) * v.cellWidth;
546
+ cell.y = position(index);
547
+ } else if (horizontal()) cell.x = position(index);
548
+ else cell.y = position(index);
549
+ }
550
+ if (horizontal()) {
551
+ v.contentWidth = position(total);
552
+ if (!v.contentHeight) v.contentHeight = height;
553
+ } else {
554
+ v.contentHeight = type === 'ListView' ? position(total) : Math.ceil(total / columns) * step();
555
+ if (!v.contentWidth) v.contentWidth = width;
556
+ }
557
+ v.headerItem = v.header;
558
+ v.footerItem = v.footer;
559
+ if (v.footer) {
560
+ v.footer.y = v.contentHeight;
561
+ v.contentHeight += v.footer.height || 0;
562
+ }
563
+ v.currentItem = instances.get(current) ?? null;
564
+ });
565
+ } finally {
566
+ busy = false;
567
+ }
568
+ };
569
+ views.set(v, { instances, layout });
570
+ v.itemAtIndex = (index) => instances.get(index) ?? null;
571
+ v.itemAt = (x, y) => v.itemAtIndex(v.indexAt(x, y));
572
+ v.indexAt = (x, y) => {
573
+ if (type === 'ListView') {
574
+ const at = horizontal() ? x : y, index = indexAtOffset(at);
575
+ const extent = measuredSizes.get(index) ?? rowSize;
576
+ return at >= 0 && index < v.count && at < position(index) + extent ? index : -1;
577
+ }
578
+ const index =
579
+ Math.floor((horizontal() ? x : y) / step()) * cols() +
580
+ (type === 'GridView' ? Math.floor(x / v.cellWidth) : 0);
581
+ return index >= 0 && index < v.count ? index : -1;
582
+ };
583
+ v.forceLayout = layout;
584
+ v.positionViewAtIndex = (index, mode = 0) => {
585
+ const at = position(index),
586
+ extent = type === 'ListView' ? measuredSizes.get(index) ?? rowSize : step(),
587
+ size = horizontal() ? v.width : v.height,
588
+ old = horizontal() ? v.contentX : v.contentY;
589
+ let next = at;
590
+ if (mode === 1) next = at - size / 2 + extent / 2;
591
+ else if (mode === 2) next = at - size + extent;
592
+ else if (mode === 3 || mode === 4)
593
+ next =
594
+ at < old ? at : at + extent > old + size ? at + extent - size : old;
595
+ if (horizontal()) v.contentX = next;
596
+ else v.contentY = next;
597
+ v.returnToBounds();
598
+ layout();
599
+ };
600
+ v.positionViewAtBeginning = () => v.positionViewAtIndex(0, 0);
601
+ v.positionViewAtEnd = () =>
602
+ v.positionViewAtIndex(Math.max(0, v.count - 1), 2);
603
+ listen(elements.get(v), 'keydown', (event) => {
604
+ if (
605
+ !v.keyNavigationEnabled ||
606
+ !['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(
607
+ event.key,
608
+ ) ||
609
+ !v.count
610
+ )
611
+ return;
612
+ const delta = ['ArrowUp', 'ArrowLeft'].includes(event.key) ? -1 : 1;
613
+ let next = v.currentIndex + delta;
614
+ if (v.keyNavigationWraps) next = (next + v.count) % v.count;
615
+ v.currentIndex = Math.max(0, Math.min(v.count - 1, next));
616
+ v.positionViewAtIndex(v.currentIndex, 4);
617
+ event.preventDefault();
618
+ });
619
+ effect(layout);
620
+ }
621
+ return { make, start, attachDelegate, rows:model=>{const data=lists.get(model)||xmlModels.get(model);if(!data)return null;data.version;return data.rows;} };
622
+ }