@gtkx/react 1.0.0-rc.1 → 1.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (381) hide show
  1. package/dist/adw/config.d.ts +2 -0
  2. package/dist/adw/config.d.ts.map +1 -0
  3. package/dist/adw/config.js +2 -0
  4. package/dist/adw/config.js.map +1 -0
  5. package/dist/{libraries/adw → adw}/dialog.d.ts +2 -2
  6. package/dist/adw/dialog.d.ts.map +1 -0
  7. package/dist/adw/dialog.js +25 -0
  8. package/dist/adw/dialog.js.map +1 -0
  9. package/dist/adw/element-behaviors.d.ts +2 -0
  10. package/dist/adw/element-behaviors.d.ts.map +1 -0
  11. package/dist/adw/element-behaviors.js +299 -0
  12. package/dist/adw/element-behaviors.js.map +1 -0
  13. package/dist/adw/element-config.d.ts +9 -0
  14. package/dist/adw/element-config.d.ts.map +1 -0
  15. package/dist/adw/element-config.js +137 -0
  16. package/dist/adw/element-config.js.map +1 -0
  17. package/dist/adw/index.d.ts +4 -0
  18. package/dist/adw/index.d.ts.map +1 -0
  19. package/dist/adw/index.js +3 -0
  20. package/dist/adw/index.js.map +1 -0
  21. package/dist/adw/prop-types.d.ts +29 -0
  22. package/dist/adw/prop-types.d.ts.map +1 -0
  23. package/dist/adw/prop-types.js +2 -0
  24. package/dist/adw/prop-types.js.map +1 -0
  25. package/dist/bootstrap.d.ts +2 -0
  26. package/dist/bootstrap.d.ts.map +1 -0
  27. package/dist/bootstrap.js +5 -0
  28. package/dist/bootstrap.js.map +1 -0
  29. package/dist/components/application.d.ts +2 -2
  30. package/dist/components/application.d.ts.map +1 -1
  31. package/dist/components/application.js +31 -18
  32. package/dist/components/application.js.map +1 -1
  33. package/dist/components/element.d.ts +6 -0
  34. package/dist/components/element.d.ts.map +1 -0
  35. package/dist/components/element.js +29 -0
  36. package/dist/components/element.js.map +1 -0
  37. package/dist/components/window.d.ts +2 -2
  38. package/dist/components/window.d.ts.map +1 -1
  39. package/dist/components/window.js +10 -11
  40. package/dist/components/window.js.map +1 -1
  41. package/dist/config.d.ts +4 -0
  42. package/dist/config.d.ts.map +1 -0
  43. package/dist/config.js +3 -0
  44. package/dist/config.js.map +1 -0
  45. package/dist/element-behaviors.d.ts +2 -0
  46. package/dist/element-behaviors.d.ts.map +1 -0
  47. package/dist/element-behaviors.js +348 -0
  48. package/dist/element-behaviors.js.map +1 -0
  49. package/dist/element-config.d.ts +10 -0
  50. package/dist/element-config.d.ts.map +1 -0
  51. package/dist/element-config.js +146 -0
  52. package/dist/element-config.js.map +1 -0
  53. package/dist/hooks/use-application.d.ts +4 -3
  54. package/dist/hooks/use-application.d.ts.map +1 -1
  55. package/dist/hooks/use-application.js +5 -4
  56. package/dist/hooks/use-application.js.map +1 -1
  57. package/dist/hooks/use-bind-setting.d.ts +21 -4
  58. package/dist/hooks/use-bind-setting.d.ts.map +1 -1
  59. package/dist/hooks/use-bind-setting.js +27 -16
  60. package/dist/hooks/use-bind-setting.js.map +1 -1
  61. package/dist/hooks/use-merged-refs.d.ts +8 -0
  62. package/dist/hooks/use-merged-refs.d.ts.map +1 -0
  63. package/dist/hooks/use-merged-refs.js +48 -0
  64. package/dist/hooks/use-merged-refs.js.map +1 -0
  65. package/dist/hooks/use-object-value.d.ts +3 -2
  66. package/dist/hooks/use-object-value.d.ts.map +1 -1
  67. package/dist/hooks/use-object-value.js +18 -21
  68. package/dist/hooks/use-object-value.js.map +1 -1
  69. package/dist/hooks/use-parent-window.d.ts +3 -2
  70. package/dist/hooks/use-parent-window.d.ts.map +1 -1
  71. package/dist/hooks/use-parent-window.js +3 -2
  72. package/dist/hooks/use-parent-window.js.map +1 -1
  73. package/dist/hooks/use-property.d.ts +5 -5
  74. package/dist/hooks/use-property.d.ts.map +1 -1
  75. package/dist/hooks/use-property.js +4 -3
  76. package/dist/hooks/use-property.js.map +1 -1
  77. package/dist/hooks/use-setting.d.ts +5 -16
  78. package/dist/hooks/use-setting.d.ts.map +1 -1
  79. package/dist/hooks/use-setting.js +16 -11
  80. package/dist/hooks/use-setting.js.map +1 -1
  81. package/dist/hooks/use-signal.d.ts +10 -7
  82. package/dist/hooks/use-signal.d.ts.map +1 -1
  83. package/dist/hooks/use-signal.js +29 -18
  84. package/dist/hooks/use-signal.js.map +1 -1
  85. package/dist/index.d.ts +7 -8
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +2 -3
  88. package/dist/index.js.map +1 -1
  89. package/dist/internal.d.ts +6 -7
  90. package/dist/internal.d.ts.map +1 -1
  91. package/dist/internal.js +5 -7
  92. package/dist/internal.js.map +1 -1
  93. package/dist/prop-types.d.ts +110 -0
  94. package/dist/prop-types.d.ts.map +1 -0
  95. package/dist/prop-types.js +2 -0
  96. package/dist/prop-types.js.map +1 -0
  97. package/dist/reconciler/apply-props.d.ts +10 -7
  98. package/dist/reconciler/apply-props.d.ts.map +1 -1
  99. package/dist/reconciler/apply-props.js +182 -84
  100. package/dist/reconciler/apply-props.js.map +1 -1
  101. package/dist/reconciler/behaviors.d.ts +59 -0
  102. package/dist/reconciler/behaviors.d.ts.map +1 -0
  103. package/dist/reconciler/behaviors.js +172 -0
  104. package/dist/reconciler/behaviors.js.map +1 -0
  105. package/dist/reconciler/child-routing.d.ts +5 -0
  106. package/dist/reconciler/child-routing.d.ts.map +1 -0
  107. package/dist/reconciler/child-routing.js +156 -0
  108. package/dist/reconciler/child-routing.js.map +1 -0
  109. package/dist/reconciler/host-config.d.ts +8 -12
  110. package/dist/reconciler/host-config.d.ts.map +1 -1
  111. package/dist/reconciler/host-config.js +175 -270
  112. package/dist/reconciler/host-config.js.map +1 -1
  113. package/dist/reconciler/instance.d.ts +4 -5
  114. package/dist/reconciler/instance.d.ts.map +1 -1
  115. package/dist/reconciler/instance.js +44 -22
  116. package/dist/reconciler/instance.js.map +1 -1
  117. package/dist/reconciler/metadata.d.ts +17 -0
  118. package/dist/reconciler/metadata.d.ts.map +1 -0
  119. package/dist/reconciler/metadata.js +82 -0
  120. package/dist/reconciler/metadata.js.map +1 -0
  121. package/dist/reconciler/node.d.ts +76 -0
  122. package/dist/reconciler/node.d.ts.map +1 -0
  123. package/dist/reconciler/node.js +53 -0
  124. package/dist/reconciler/node.js.map +1 -0
  125. package/dist/reconciler/placement.d.ts +5 -0
  126. package/dist/reconciler/placement.d.ts.map +1 -0
  127. package/dist/reconciler/placement.js +175 -0
  128. package/dist/reconciler/placement.js.map +1 -0
  129. package/dist/reconciler/registry.d.ts +92 -0
  130. package/dist/reconciler/registry.d.ts.map +1 -0
  131. package/dist/reconciler/registry.js +64 -0
  132. package/dist/reconciler/registry.js.map +1 -0
  133. package/dist/reconciler/root-element.d.ts +9 -12
  134. package/dist/reconciler/root-element.d.ts.map +1 -1
  135. package/dist/reconciler/root-element.js +6 -6
  136. package/dist/reconciler/root-element.js.map +1 -1
  137. package/dist/reconciler/root.d.ts +56 -0
  138. package/dist/reconciler/root.d.ts.map +1 -0
  139. package/dist/reconciler/root.js +109 -0
  140. package/dist/reconciler/root.js.map +1 -0
  141. package/dist/reconciler/signals.d.ts +10 -0
  142. package/dist/reconciler/signals.d.ts.map +1 -0
  143. package/dist/reconciler/signals.js +70 -0
  144. package/dist/reconciler/signals.js.map +1 -0
  145. package/dist/reconciler/text.d.ts +19 -0
  146. package/dist/reconciler/text.d.ts.map +1 -0
  147. package/dist/reconciler/text.js +276 -0
  148. package/dist/reconciler/text.js.map +1 -0
  149. package/dist/utils/accessible-metadata.d.ts +11 -4
  150. package/dist/utils/accessible-metadata.d.ts.map +1 -1
  151. package/dist/utils/accessible-metadata.js +20 -17
  152. package/dist/utils/accessible-metadata.js.map +1 -1
  153. package/dist/{reconciler/accessible.d.ts → utils/accessible-props.d.ts} +7 -6
  154. package/dist/utils/accessible-props.d.ts.map +1 -0
  155. package/dist/utils/accessible-props.js +181 -0
  156. package/dist/utils/accessible-props.js.map +1 -0
  157. package/dist/utils/ref-prop.d.ts +15 -0
  158. package/dist/utils/ref-prop.d.ts.map +1 -0
  159. package/dist/utils/ref-prop.js +18 -0
  160. package/dist/utils/ref-prop.js.map +1 -0
  161. package/dist/utils/settings.d.ts +17 -0
  162. package/dist/utils/settings.d.ts.map +1 -0
  163. package/dist/utils/settings.js +37 -0
  164. package/dist/utils/settings.js.map +1 -0
  165. package/dist/utils/variant.d.ts +57 -0
  166. package/dist/utils/variant.d.ts.map +1 -0
  167. package/dist/utils/variant.js +197 -0
  168. package/dist/utils/variant.js.map +1 -0
  169. package/env.d.ts +9 -0
  170. package/package.json +36 -11
  171. package/src/adw/config.ts +1 -0
  172. package/src/adw/dialog.tsx +35 -0
  173. package/src/adw/element-behaviors.ts +379 -0
  174. package/src/adw/element-config.ts +140 -0
  175. package/src/adw/index.ts +4 -0
  176. package/src/adw/prop-types.ts +42 -0
  177. package/src/bootstrap.ts +5 -0
  178. package/src/components/application.tsx +42 -23
  179. package/src/components/element.tsx +47 -0
  180. package/src/components/window.tsx +12 -13
  181. package/src/config.ts +3 -0
  182. package/src/element-behaviors.ts +424 -0
  183. package/src/element-config.ts +148 -0
  184. package/src/env.d.ts +1 -0
  185. package/src/hooks/use-application.ts +6 -4
  186. package/src/hooks/use-bind-setting.ts +47 -31
  187. package/src/hooks/use-merged-refs.ts +64 -0
  188. package/src/hooks/use-object-value.ts +27 -23
  189. package/src/hooks/use-parent-window.ts +4 -2
  190. package/src/hooks/use-property.ts +11 -10
  191. package/src/hooks/use-setting.ts +20 -33
  192. package/src/hooks/use-signal.ts +37 -48
  193. package/src/index.ts +7 -8
  194. package/src/internal.ts +7 -18
  195. package/src/prop-types.ts +142 -0
  196. package/src/reconciler/apply-props.ts +233 -85
  197. package/src/reconciler/behaviors.ts +324 -0
  198. package/src/reconciler/child-routing.ts +202 -0
  199. package/src/reconciler/host-config.ts +239 -366
  200. package/src/reconciler/instance.ts +76 -24
  201. package/src/reconciler/metadata.ts +119 -0
  202. package/src/reconciler/node.ts +161 -0
  203. package/src/reconciler/placement.ts +255 -0
  204. package/src/reconciler/registry.ts +161 -0
  205. package/src/reconciler/root-element.ts +9 -11
  206. package/src/reconciler/root.ts +175 -0
  207. package/src/reconciler/signals.ts +113 -0
  208. package/src/reconciler/text.ts +407 -0
  209. package/src/utils/accessible-metadata.ts +23 -18
  210. package/src/utils/accessible-props.ts +279 -0
  211. package/src/utils/ref-prop.ts +30 -0
  212. package/src/utils/settings.ts +69 -0
  213. package/src/utils/variant.ts +368 -0
  214. package/dist/hooks/use-merge-refs.d.ts +0 -3
  215. package/dist/hooks/use-merge-refs.d.ts.map +0 -1
  216. package/dist/hooks/use-merge-refs.js +0 -48
  217. package/dist/hooks/use-merge-refs.js.map +0 -1
  218. package/dist/hooks/use-object-attachment.d.ts +0 -10
  219. package/dist/hooks/use-object-attachment.d.ts.map +0 -1
  220. package/dist/hooks/use-object-attachment.js +0 -24
  221. package/dist/hooks/use-object-attachment.js.map +0 -1
  222. package/dist/libraries/adw/dialog.d.ts.map +0 -1
  223. package/dist/libraries/adw/dialog.js +0 -25
  224. package/dist/libraries/adw/dialog.js.map +0 -1
  225. package/dist/libraries/adw/index.d.ts +0 -2
  226. package/dist/libraries/adw/index.d.ts.map +0 -1
  227. package/dist/libraries/adw/index.js +0 -2
  228. package/dist/libraries/adw/index.js.map +0 -1
  229. package/dist/reconciler/accessible.d.ts.map +0 -1
  230. package/dist/reconciler/accessible.js +0 -143
  231. package/dist/reconciler/accessible.js.map +0 -1
  232. package/dist/reconciler/commit-flush.d.ts +0 -3
  233. package/dist/reconciler/commit-flush.d.ts.map +0 -1
  234. package/dist/reconciler/commit-flush.js +0 -13
  235. package/dist/reconciler/commit-flush.js.map +0 -1
  236. package/dist/reconciler/container-attach.d.ts +0 -10
  237. package/dist/reconciler/container-attach.d.ts.map +0 -1
  238. package/dist/reconciler/container-attach.js +0 -216
  239. package/dist/reconciler/container-attach.js.map +0 -1
  240. package/dist/reconciler/content-rebuild.d.ts +0 -3
  241. package/dist/reconciler/content-rebuild.d.ts.map +0 -1
  242. package/dist/reconciler/content-rebuild.js +0 -15
  243. package/dist/reconciler/content-rebuild.js.map +0 -1
  244. package/dist/reconciler/dispatch.d.ts +0 -6
  245. package/dist/reconciler/dispatch.d.ts.map +0 -1
  246. package/dist/reconciler/dispatch.js +0 -61
  247. package/dist/reconciler/dispatch.js.map +0 -1
  248. package/dist/reconciler/element-prop-appliers.d.ts +0 -5
  249. package/dist/reconciler/element-prop-appliers.d.ts.map +0 -1
  250. package/dist/reconciler/element-prop-appliers.js +0 -104
  251. package/dist/reconciler/element-prop-appliers.js.map +0 -1
  252. package/dist/reconciler/element-props.d.ts +0 -25
  253. package/dist/reconciler/element-props.d.ts.map +0 -1
  254. package/dist/reconciler/element-props.js +0 -157
  255. package/dist/reconciler/element-props.js.map +0 -1
  256. package/dist/reconciler/label-text-rebuild.d.ts +0 -3
  257. package/dist/reconciler/label-text-rebuild.d.ts.map +0 -1
  258. package/dist/reconciler/label-text-rebuild.js +0 -22
  259. package/dist/reconciler/label-text-rebuild.js.map +0 -1
  260. package/dist/reconciler/portal.d.ts +0 -11
  261. package/dist/reconciler/portal.d.ts.map +0 -1
  262. package/dist/reconciler/portal.js +0 -20
  263. package/dist/reconciler/portal.js.map +0 -1
  264. package/dist/reconciler/reconciler-error-handler.d.ts +0 -7
  265. package/dist/reconciler/reconciler-error-handler.d.ts.map +0 -1
  266. package/dist/reconciler/reconciler-error-handler.js +0 -24
  267. package/dist/reconciler/reconciler-error-handler.js.map +0 -1
  268. package/dist/reconciler/reconciler-root.d.ts +0 -19
  269. package/dist/reconciler/reconciler-root.d.ts.map +0 -1
  270. package/dist/reconciler/reconciler-root.js +0 -24
  271. package/dist/reconciler/reconciler-root.js.map +0 -1
  272. package/dist/reconciler/reconciler.d.ts +0 -3
  273. package/dist/reconciler/reconciler.d.ts.map +0 -1
  274. package/dist/reconciler/reconciler.js +0 -12
  275. package/dist/reconciler/reconciler.js.map +0 -1
  276. package/dist/reconciler/render.d.ts +0 -23
  277. package/dist/reconciler/render.d.ts.map +0 -1
  278. package/dist/reconciler/render.js +0 -46
  279. package/dist/reconciler/render.js.map +0 -1
  280. package/dist/reconciler/signal-store.d.ts +0 -22
  281. package/dist/reconciler/signal-store.d.ts.map +0 -1
  282. package/dist/reconciler/signal-store.js +0 -84
  283. package/dist/reconciler/signal-store.js.map +0 -1
  284. package/dist/reconciler/state.d.ts +0 -36
  285. package/dist/reconciler/state.d.ts.map +0 -1
  286. package/dist/reconciler/state.js +0 -38
  287. package/dist/reconciler/state.js.map +0 -1
  288. package/dist/reconciler/text-buffer-controller.d.ts +0 -17
  289. package/dist/reconciler/text-buffer-controller.d.ts.map +0 -1
  290. package/dist/reconciler/text-buffer-controller.js +0 -102
  291. package/dist/reconciler/text-buffer-controller.js.map +0 -1
  292. package/dist/reconciler/text-buffer-rebuild.d.ts +0 -3
  293. package/dist/reconciler/text-buffer-rebuild.d.ts.map +0 -1
  294. package/dist/reconciler/text-buffer-rebuild.js +0 -7
  295. package/dist/reconciler/text-buffer-rebuild.js.map +0 -1
  296. package/dist/reconciler/text-node.d.ts +0 -7
  297. package/dist/reconciler/text-node.d.ts.map +0 -1
  298. package/dist/reconciler/text-node.js +0 -8
  299. package/dist/reconciler/text-node.js.map +0 -1
  300. package/dist/reconciler/types.d.ts +0 -5
  301. package/dist/reconciler/types.d.ts.map +0 -1
  302. package/dist/reconciler/types.js +0 -2
  303. package/dist/reconciler/types.js.map +0 -1
  304. package/dist/reconciler/visibility.d.ts +0 -6
  305. package/dist/reconciler/visibility.d.ts.map +0 -1
  306. package/dist/reconciler/visibility.js +0 -57
  307. package/dist/reconciler/visibility.js.map +0 -1
  308. package/dist/reconciler/wrapper-apply.d.ts +0 -6
  309. package/dist/reconciler/wrapper-apply.d.ts.map +0 -1
  310. package/dist/reconciler/wrapper-apply.js +0 -287
  311. package/dist/reconciler/wrapper-apply.js.map +0 -1
  312. package/dist/reconciler/wrapper-content.d.ts +0 -8
  313. package/dist/reconciler/wrapper-content.d.ts.map +0 -1
  314. package/dist/reconciler/wrapper-content.js +0 -19
  315. package/dist/reconciler/wrapper-content.js.map +0 -1
  316. package/dist/reconciler/wrapper-node.d.ts +0 -8
  317. package/dist/reconciler/wrapper-node.d.ts.map +0 -1
  318. package/dist/reconciler/wrapper-node.js +0 -4
  319. package/dist/reconciler/wrapper-node.js.map +0 -1
  320. package/dist/reconciler/wrapper-protocol.d.ts +0 -13
  321. package/dist/reconciler/wrapper-protocol.d.ts.map +0 -1
  322. package/dist/reconciler/wrapper-protocol.js +0 -20
  323. package/dist/reconciler/wrapper-protocol.js.map +0 -1
  324. package/dist/utils/create-element-component.d.ts +0 -4
  325. package/dist/utils/create-element-component.d.ts.map +0 -1
  326. package/dist/utils/create-element-component.js +0 -54
  327. package/dist/utils/create-element-component.js.map +0 -1
  328. package/dist/utils/gtype.d.ts +0 -16
  329. package/dist/utils/gtype.d.ts.map +0 -1
  330. package/dist/utils/gtype.js +0 -127
  331. package/dist/utils/gtype.js.map +0 -1
  332. package/dist/utils/notify-name.d.ts +0 -5
  333. package/dist/utils/notify-name.d.ts.map +0 -1
  334. package/dist/utils/notify-name.js +0 -6
  335. package/dist/utils/notify-name.js.map +0 -1
  336. package/dist/utils/object-prop.d.ts +0 -8
  337. package/dist/utils/object-prop.d.ts.map +0 -1
  338. package/dist/utils/object-prop.js +0 -9
  339. package/dist/utils/object-prop.js.map +0 -1
  340. package/dist/utils/settings-accessor.d.ts +0 -8
  341. package/dist/utils/settings-accessor.d.ts.map +0 -1
  342. package/dist/utils/settings-accessor.js +0 -69
  343. package/dist/utils/settings-accessor.js.map +0 -1
  344. package/dist/utils/wrapper-node-jsx.d.ts +0 -32
  345. package/dist/utils/wrapper-node-jsx.d.ts.map +0 -1
  346. package/dist/utils/wrapper-node-jsx.js +0 -2
  347. package/dist/utils/wrapper-node-jsx.js.map +0 -1
  348. package/src/hooks/use-merge-refs.ts +0 -53
  349. package/src/hooks/use-object-attachment.ts +0 -35
  350. package/src/libraries/adw/dialog.tsx +0 -34
  351. package/src/libraries/adw/index.ts +0 -1
  352. package/src/reconciler/accessible.ts +0 -232
  353. package/src/reconciler/commit-flush.ts +0 -13
  354. package/src/reconciler/container-attach.ts +0 -219
  355. package/src/reconciler/content-rebuild.ts +0 -19
  356. package/src/reconciler/dispatch.ts +0 -58
  357. package/src/reconciler/element-prop-appliers.ts +0 -110
  358. package/src/reconciler/element-props.ts +0 -192
  359. package/src/reconciler/label-text-rebuild.ts +0 -26
  360. package/src/reconciler/portal.ts +0 -28
  361. package/src/reconciler/reconciler-error-handler.ts +0 -29
  362. package/src/reconciler/reconciler-root.ts +0 -57
  363. package/src/reconciler/reconciler.ts +0 -13
  364. package/src/reconciler/render.ts +0 -61
  365. package/src/reconciler/signal-store.ts +0 -103
  366. package/src/reconciler/state.ts +0 -72
  367. package/src/reconciler/text-buffer-controller.ts +0 -107
  368. package/src/reconciler/text-buffer-rebuild.ts +0 -12
  369. package/src/reconciler/text-node.ts +0 -13
  370. package/src/reconciler/types.ts +0 -6
  371. package/src/reconciler/visibility.ts +0 -52
  372. package/src/reconciler/wrapper-apply.ts +0 -308
  373. package/src/reconciler/wrapper-content.ts +0 -24
  374. package/src/reconciler/wrapper-node.ts +0 -8
  375. package/src/reconciler/wrapper-protocol.ts +0 -32
  376. package/src/utils/create-element-component.ts +0 -77
  377. package/src/utils/gtype.ts +0 -166
  378. package/src/utils/notify-name.ts +0 -10
  379. package/src/utils/object-prop.ts +0 -13
  380. package/src/utils/settings-accessor.ts +0 -89
  381. package/src/utils/wrapper-node-jsx.ts +0 -34
@@ -1,117 +1,265 @@
1
1
  import type * as GObject from "@gtkx/gi/gobject";
2
2
  import type { SignalHandler } from "@gtkx/runtime";
3
- import { isConstructOnlyProp, resolveDefaultProp, resolveSignal } from "../utils/gtype.js";
4
- import { NOTIFY_DETAIL_PREFIX, notifyDetailToProp } from "../utils/notify-name.js";
5
- import { stateOf } from "./state.js";
6
- import type { Props } from "./types.js";
3
+ import * as Gtk from "@gtkx/gi/gtk";
4
+ import { drain, kebabCase } from "@gtkx/utils";
5
+ import type { ElementBehavior, Props } from "./registry.js";
6
+ import { applyAccessibleProps, isAccessibleProp } from "../utils/accessible-props.js";
7
+ import { type TypeInfo, typeInfoFor } from "./metadata.js";
8
+ import { type ElementNode, getOrCreateContext, type SignalTarget } from "./node.js";
9
+ import { connectHandler, disconnectHandler } from "./signals.js";
10
+ import { bufferText, hasSameText, isContentPaintableProp, markTextDirty, TEXT_PROP } from "./text.js";
7
11
 
8
- const notifyValueHandler = (container: GObject.Object, signalName: string, handler: SignalHandler): SignalHandler => {
9
- const prop = notifyDetailToProp(signalName);
10
- return () => handler(Reflect.get(container, prop), container);
12
+ type PropDelta = { name: string; value: unknown; prevValue: unknown };
13
+ type BehaviorUpdateContext = { node: ElementNode; prev: Props; next: Props; consumed: Set<string> };
14
+ type PropChange = { prev: Props; next: Props };
15
+
16
+ const REACT_RESERVED_PROPS = new Set(["children", "ref", "key"]);
17
+ const NOTIFY_PREFIX = "onNotify";
18
+ const HANDLER_PREFIX = "on";
19
+ const flushDirty: Set<ElementNode> = new Set();
20
+
21
+ const isHandlerName = (name: string): boolean => /^on[A-Z]/.test(name);
22
+
23
+ const signalForProp = (info: TypeInfo, name: string): string => {
24
+ if (name === NOTIFY_PREFIX) {
25
+ return "notify";
26
+ }
27
+
28
+ if (name.startsWith(NOTIFY_PREFIX) && name.length > NOTIFY_PREFIX.length) {
29
+ return `notify::${kebabCase(name.slice(NOTIFY_PREFIX.length))}`;
30
+ }
31
+
32
+ return info.signals[name] ?? kebabCase(name.slice(HANDLER_PREFIX.length));
11
33
  };
12
34
 
13
- const isPlainObject = (value: unknown): value is Record<string, unknown> => {
14
- if (typeof value !== "object" || value === null) return false;
15
- const proto = Object.getPrototypeOf(value);
16
- return proto === Object.prototype || proto === null;
35
+ const isReservedName = (name: string, info: TypeInfo): boolean =>
36
+ REACT_RESERVED_PROPS.has(name) || isHandlerName(name) || isAccessibleProp(name) || info.constructOnly.has(name);
37
+
38
+ const isSkippedValueName = (name: string, info: TypeInfo, consumed: Set<string>): boolean =>
39
+ isReservedName(name, info) || consumed.has(name);
40
+
41
+ const writeValue = (object: GObject.Object, name: string, value: unknown): void => {
42
+ if (hasSameText(object, name, value)) {
43
+ return;
44
+ }
45
+
46
+ Reflect.set(object, name, value);
17
47
  };
18
48
 
19
- const elementsEqual = (a: unknown, b: unknown): boolean => {
20
- if (a === b) return true;
21
- if (!isPlainObject(a) || !isPlainObject(b)) return false;
22
- const keys = Object.keys(a);
23
- return keys.length === Object.keys(b).length && keys.every((key) => Object.hasOwn(b, key) && a[key] === b[key]);
49
+ const resetPlain = (object: GObject.Object, info: TypeInfo, name: string): void => {
50
+ if (Object.hasOwn(info.defaults, name)) {
51
+ writeValue(object, name, info.defaults[name]);
52
+ }
24
53
  };
25
54
 
26
- const propsEqual = (a: unknown, b: unknown): boolean => {
27
- if (a === b) return true;
28
- if (Array.isArray(a) && Array.isArray(b)) {
29
- return a.length === b.length && a.every((item, index) => elementsEqual(item, b[index]));
55
+ const setOrReset = (object: GObject.Object, info: TypeInfo, name: string, value: unknown): void => {
56
+ if (value === undefined) {
57
+ resetPlain(object, info, name);
58
+ } else {
59
+ writeValue(object, name, value);
30
60
  }
31
- return false;
32
61
  };
33
62
 
34
- type ApplyPropsOptions = {
35
- exclude?: (name: string) => boolean;
63
+ const applyBufferText = (buffer: Gtk.TextBuffer, text: string): void => {
64
+ if (bufferText(buffer) === text) {
65
+ return;
66
+ }
67
+
68
+ buffer.beginIrreversibleAction();
69
+ buffer.setText(text, -1);
70
+ buffer.endIrreversibleAction();
36
71
  };
37
72
 
38
- type PendingSignal = { signalName: string; newValue: unknown };
73
+ const isBufferText = (node: ElementNode, name: string): node is ElementNode & { object: Gtk.TextBuffer } =>
74
+ name === TEXT_PROP && node.contentKind === "buffer" && node.object instanceof Gtk.TextBuffer;
75
+
76
+ const propText = (value: unknown): string => (typeof value === "string" ? value : JSON.stringify(value));
77
+
78
+ const applyEntry = (node: ElementNode, info: TypeInfo, delta: PropDelta): void => {
79
+ const { name, value, prevValue } = delta;
80
+
81
+ if (value !== undefined && isBufferText(node, name)) {
82
+ applyBufferText(node.object, propText(value));
83
+
84
+ return;
85
+ }
86
+
87
+ if (isContentPaintableProp(node, name)) {
88
+ markTextDirty(node);
39
89
 
40
- type PendingProperty = { name: string; newValue: unknown };
90
+ return;
91
+ }
92
+
93
+ const isInitialNull = value === null && prevValue === undefined;
41
94
 
42
- const collectGenericChanges = (
43
- container: GObject.Object,
44
- oldProps: Props | null,
45
- newProps: Props,
46
- exclude: ((name: string) => boolean) | undefined,
47
- ): { pendingSignals: PendingSignal[]; pendingProperties: PendingProperty[] } => {
48
- const constructionApplied = oldProps === null;
49
- const pendingSignals: PendingSignal[] = [];
50
- const pendingProperties: PendingProperty[] = [];
95
+ if (!isInitialNull) {
96
+ setOrReset(node.object, info, name, value);
97
+ }
98
+ };
51
99
 
52
- const collect = (name: string): void => {
53
- if (name === "children" || exclude?.(name)) return;
54
- if (isConstructOnlyProp(container, name)) return;
100
+ const eachChangedName = (prev: Props, next: Props, visit: (name: string) => void): void => {
101
+ const names = new Set([...Object.keys(prev), ...Object.keys(next)]);
55
102
 
56
- const oldValue = oldProps?.[name];
57
- const newValue = newProps[name];
58
- if (propsEqual(oldValue, newValue)) return;
103
+ for (const name of names) {
104
+ visit(name);
105
+ }
106
+ };
59
107
 
60
- const signalName = resolveSignal(container, name);
61
- if (signalName) {
62
- pendingSignals.push({ signalName, newValue });
108
+ const constructOnlyChangeError = (typeName: string, name: string): Error =>
109
+ new Error(
110
+ `Cannot change the construct-only prop '${name}' of <${typeName}> after it is created. ` +
111
+ "GTK accepts it only while the object is being built, so give the element a key that " +
112
+ `changes with '${name}' and React will build a new one.`,
113
+ );
114
+
115
+ const assertConstructOnlyUnchanged = (typeName: string, prev: Props, next: Props): void => {
116
+ const info = typeInfoFor(typeName);
117
+
118
+ eachChangedName(prev, next, (name) => {
119
+ if (!info.constructOnly.has(name) || Object.is(prev[name], next[name])) {
63
120
  return;
64
121
  }
65
- if (constructionApplied) return;
66
- if (newValue !== undefined) {
67
- pendingProperties.push({ name, newValue });
122
+
123
+ throw constructOnlyChangeError(typeName, name);
124
+ });
125
+ };
126
+
127
+ const collectConsumed = (ctx: BehaviorUpdateContext, behavior: ElementBehavior): void => {
128
+ if (behavior.update === undefined) {
129
+ return;
130
+ }
131
+
132
+ const result = behavior.update(ctx.node.object, ctx.prev, ctx.next, getOrCreateContext(ctx.node, behavior));
133
+
134
+ if (result === undefined) {
135
+ return;
136
+ }
137
+
138
+ for (const name of result) {
139
+ ctx.consumed.add(name);
140
+ }
141
+ };
142
+
143
+ const runBehaviorUpdates = (node: ElementNode, info: TypeInfo, prev: Props, next: Props): Set<string> => {
144
+ const consumed: Set<string> = new Set();
145
+ const ctx: BehaviorUpdateContext = { node, prev, next, consumed };
146
+
147
+ for (const behavior of info.behaviors) {
148
+ collectConsumed(ctx, behavior);
149
+ }
150
+
151
+ return consumed;
152
+ };
153
+
154
+ const applyValueEntries = (node: ElementNode, info: TypeInfo, change: PropChange, consumed: Set<string>): void => {
155
+ eachChangedName(change.prev, change.next, (name) => {
156
+ if (isSkippedValueName(name, info, consumed) || Object.is(change.prev[name], change.next[name])) {
68
157
  return;
69
158
  }
70
- const fallback = resolveDefaultProp(container, name);
71
- if (fallback.has) pendingProperties.push({ name, newValue: fallback.value });
72
- };
73
-
74
- if (oldProps) {
75
- for (const name in oldProps) collect(name);
76
- for (const name in newProps) {
77
- if (!(name in oldProps)) collect(name);
159
+
160
+ applyEntry(node, info, { name, value: change.next[name], prevValue: change.prev[name] });
161
+ });
162
+ };
163
+
164
+ const restoreActionableSensitivity = (node: ElementNode, info: TypeInfo, prev: Props, next: Props): void => {
165
+ if (prev.actionName === undefined || next.actionName !== undefined) {
166
+ return;
167
+ }
168
+
169
+ const desired = "sensitive" in next ? next.sensitive : info.defaults.sensitive;
170
+
171
+ if (typeof desired === "boolean") {
172
+ Reflect.set(node.object, "sensitive", desired);
173
+ }
174
+ };
175
+
176
+ const applyHandlers = (target: SignalTarget, info: TypeInfo, prev: Props, next: Props): void => {
177
+ eachChangedName(prev, next, (name) => {
178
+ if (!isHandlerName(name)) {
179
+ return;
78
180
  }
79
- } else {
80
- for (const name in newProps) collect(name);
181
+
182
+ const value = next[name];
183
+
184
+ if (typeof value === "function") {
185
+ connectHandler(target, name, signalForProp(info, name), value as SignalHandler);
186
+ } else {
187
+ disconnectHandler(target, name);
188
+ }
189
+ });
190
+ };
191
+
192
+ const markFlush = (node: ElementNode): void => {
193
+ if (typeInfoFor(node.typeName).hasFlush) {
194
+ flushDirty.add(node);
81
195
  }
196
+ };
82
197
 
83
- return { pendingSignals, pendingProperties };
198
+ const eachBehavior = (node: ElementNode, visit: (behavior: ElementBehavior, context: unknown) => void): void => {
199
+ for (const behavior of typeInfoFor(node.typeName).behaviors) {
200
+ visit(behavior, getOrCreateContext(node, behavior));
201
+ }
84
202
  };
85
203
 
86
- export function applyProps(
87
- container: GObject.Object,
88
- oldProps: Props | null,
89
- newProps: Props,
90
- options?: ApplyPropsOptions,
91
- ): void {
92
- const { signalStore } = stateOf(container);
93
- const { pendingSignals, pendingProperties } = collectGenericChanges(
94
- container,
95
- oldProps,
96
- newProps,
97
- options?.exclude,
98
- );
204
+ const flushBehaviors = (): void => {
205
+ drain(flushDirty, (node) => {
206
+ eachBehavior(node, (behavior, context) => behavior.flush?.(node.object, context));
207
+ });
208
+ };
99
209
 
100
- for (const { signalName, newValue } of pendingSignals) {
101
- const nextHandler = typeof newValue === "function" ? (newValue as SignalHandler) : undefined;
102
- const handler =
103
- nextHandler && signalName.startsWith(NOTIFY_DETAIL_PREFIX)
104
- ? notifyValueHandler(container, signalName, nextHandler)
105
- : nextHandler;
106
- signalStore.set({
107
- instance: container,
108
- signal: signalName,
109
- handler,
110
- });
210
+ const mountBehaviors = (node: ElementNode): void => {
211
+ if (!typeInfoFor(node.typeName).hasMount) {
212
+ return;
111
213
  }
112
214
 
113
- for (const { name, newValue } of pendingProperties) {
114
- if (typeof newValue === "string" && Reflect.get(container, name) === newValue) continue;
115
- Reflect.set(container, name, newValue);
215
+ eachBehavior(node, (behavior, context) => behavior.mount?.(node.object, context));
216
+ };
217
+
218
+ const unmountBehaviors = (node: ElementNode): void => {
219
+ eachBehavior(node, (behavior, context) => behavior.unmount?.(node.object, context));
220
+ };
221
+
222
+ const applyAccessible = (object: GObject.Object, prev: Props, next: Props): void => {
223
+ if (object instanceof Gtk.Widget) {
224
+ applyAccessibleProps(object, prev, next);
116
225
  }
117
- }
226
+ };
227
+
228
+ const applyElementProps = (node: ElementNode, prev: Props, next: Props): void => {
229
+ const info = typeInfoFor(node.typeName);
230
+ const consumed = runBehaviorUpdates(node, info, prev, next);
231
+ applyValueEntries(node, info, { prev, next }, consumed);
232
+ restoreActionableSensitivity(node, info, prev, next);
233
+ applyAccessible(node.object, prev, next);
234
+ applyHandlers(node, info, prev, next);
235
+ markFlush(node);
236
+ node.props = next;
237
+ };
238
+
239
+ const isSkippedAdoptedName = (info: TypeInfo, name: string): boolean => isReservedName(name, info);
240
+
241
+ const applyAdoptedProps = (target: SignalTarget, prev: Props, next: Props): void => {
242
+ const { object, typeName } = target;
243
+ const info = typeInfoFor(typeName);
244
+
245
+ eachChangedName(prev, next, (name) => {
246
+ if (isSkippedAdoptedName(info, name) || Object.is(prev[name], next[name])) {
247
+ return;
248
+ }
249
+
250
+ setOrReset(object, info, name, next[name]);
251
+ });
252
+
253
+ applyAccessible(object, prev, next);
254
+ applyHandlers(target, info, prev, next);
255
+ };
256
+
257
+ export {
258
+ markFlush,
259
+ flushBehaviors,
260
+ mountBehaviors,
261
+ unmountBehaviors,
262
+ applyElementProps,
263
+ applyAdoptedProps,
264
+ assertConstructOnlyUnchanged,
265
+ };
@@ -0,0 +1,324 @@
1
+ import type * as GObject from "@gtkx/gi/gobject";
2
+ import type * as Gtk from "@gtkx/gi/gtk";
3
+ import { getInstanceType, TYPE_INVALID, typeFromName, typeIsA } from "@gtkx/runtime";
4
+ import { getOrInsert, isDeepEqual, structuredClone } from "@gtkx/utils";
5
+ import type { DetachInfo, ElementBehavior, PlaceInfo, Props } from "./registry.js";
6
+ import { hasSameText } from "./text.js";
7
+
8
+ type SlotHooks<P extends GObject.Object, C extends GObject.Object> = {
9
+ attach: (parent: P, child: C, info: PlaceInfo) => unknown;
10
+ detach?: (parent: P, child: C, info: DetachInfo) => void;
11
+ reorder?: (parent: P, child: C, info: PlaceInfo) => unknown;
12
+ resolve?: (parent: P, child: C) => GObject.Object | null;
13
+ };
14
+
15
+ type ValueApply<P extends GObject.Object, V> = (object: P, value: V) => void;
16
+
17
+ type ListHooks<P extends GObject.Object, I, H> = {
18
+ add?: (parent: P, item: I) => H;
19
+ remove?: (parent: P, item: I, handle: H) => void;
20
+ clear?: (parent: P) => void;
21
+ };
22
+
23
+ type ListEntry = { item: unknown; handle: unknown };
24
+ type ListState = { snapshot: unknown[]; entries: ListEntry[] };
25
+ type DeferredState = { desired: unknown; present: boolean; applied: unknown };
26
+ type CanApply<P extends GObject.Object, V> = (object: P, value: V) => boolean;
27
+ type ChildSetter = GObject.Object & { setChild: (child: Gtk.Widget | null) => void };
28
+ type ContentSetter<C extends Gtk.Widget> = GObject.Object & { setContent: (content: C | null) => void };
29
+
30
+ type BoxLike = GObject.Object & {
31
+ remove: (child: Gtk.Widget) => void;
32
+ insertChildAfter: (child: Gtk.Widget, sibling: Gtk.Widget | null) => unknown;
33
+ reorderChildAfter: (child: Gtk.Widget, sibling: Gtk.Widget | null) => void;
34
+ };
35
+
36
+ type RowCache = WeakMap<GObject.Object, Gtk.Widget>;
37
+
38
+ type IndexedInserter = GObject.Object & {
39
+ remove: (child: Gtk.Widget) => void;
40
+ insert: (child: Gtk.Widget, position: number) => unknown;
41
+ };
42
+
43
+ const childTypeCache: Map<string, bigint> = new Map();
44
+
45
+ const childTypeFor = (name: string): bigint => getOrInsert(childTypeCache, name, typeFromName);
46
+
47
+ const childMatcher =
48
+ (name: string): ((child: GObject.Object) => boolean) =>
49
+ (child) => {
50
+ const type = childTypeFor(name);
51
+
52
+ return type === TYPE_INVALID || typeIsA(getInstanceType(child), type);
53
+ };
54
+
55
+ const slotAttach =
56
+ <P extends GObject.Object, C extends GObject.Object>(
57
+ slotName: string,
58
+ isMatch: (child: GObject.Object) => boolean,
59
+ attach: SlotHooks<P, C>["attach"],
60
+ ): NonNullable<ElementBehavior["attach"]> =>
61
+ (object, child, info) => {
62
+ if (info.slot !== slotName || !isMatch(child)) {
63
+ return;
64
+ }
65
+
66
+ return attach(object as P, child as C, info) ?? true;
67
+ };
68
+
69
+ /** Builds a behavior for a named child slot holding children of `childType`, claiming matches only. */
70
+ const slot = <P extends GObject.Object, C extends GObject.Object>(
71
+ slotName: string,
72
+ childType: string,
73
+ hooks: SlotHooks<P, C>,
74
+ ): ElementBehavior => {
75
+ const matches = childMatcher(childType);
76
+ const { attach, detach, reorder, resolve } = hooks;
77
+ const behavior: ElementBehavior = { attach: slotAttach(slotName, matches, attach) };
78
+
79
+ if (reorder !== undefined) {
80
+ behavior.reorder = (object, child, info) => reorder(object as P, child as C, info) ?? true;
81
+ }
82
+
83
+ if (detach !== undefined) {
84
+ behavior.detach = (object, child, info) => {
85
+ detach(object as P, child as C, info);
86
+ };
87
+ }
88
+
89
+ if (resolve !== undefined) {
90
+ behavior.resolve = (object, child) => resolve(object as P, child as C);
91
+ }
92
+
93
+ return behavior;
94
+ };
95
+
96
+ /** Builds a scalar-prop behavior that invokes `apply` whenever the value changes, and claims the prop. */
97
+ const value = <P extends GObject.Object, V>(
98
+ prop: string,
99
+ apply: ValueApply<P, V>,
100
+ ): ElementBehavior<P> => ({
101
+ update: (object, prev, next) => {
102
+ if (!Object.is(prev[prop], next[prop]) && next[prop] !== undefined) {
103
+ apply(object, next[prop] as V);
104
+ }
105
+
106
+ return [prop];
107
+ },
108
+ });
109
+
110
+ const teardownList = <P extends GObject.Object, I, H>(
111
+ object: P,
112
+ entries: ListEntry[],
113
+ hooks: ListHooks<P, I, H>,
114
+ ): void => {
115
+ if (hooks.clear !== undefined) {
116
+ hooks.clear(object);
117
+
118
+ return;
119
+ }
120
+
121
+ const remove = hooks.remove;
122
+
123
+ if (remove === undefined) {
124
+ return;
125
+ }
126
+
127
+ for (const entry of entries) {
128
+ remove(object, entry.item as I, entry.handle as H);
129
+ }
130
+ };
131
+
132
+ /**
133
+ * Builds an array-prop behavior that adds, removes, and clears its items, reapplying on structural
134
+ * change. `add` may return a handle that the same item's later `remove` receives, for items whose
135
+ * teardown needs what `add` produced (as VFL constraints need the objects the layout created).
136
+ */
137
+ const list = <P extends GObject.Object, I, H = void>(
138
+ prop: string,
139
+ hooks: ListHooks<P, I, H>,
140
+ ): ElementBehavior<P> => {
141
+ const { add } = hooks;
142
+
143
+ return {
144
+ createContext: (): ListState => ({ snapshot: [], entries: [] }),
145
+ update: (object, _prev, next, context) => {
146
+ const state = context as ListState;
147
+ const raw = next[prop];
148
+ const items: unknown[] = Array.isArray(raw) ? raw : [];
149
+
150
+ if (isDeepEqual(state.snapshot, items)) {
151
+ return [prop];
152
+ }
153
+
154
+ teardownList(object, state.entries, hooks);
155
+ state.entries = items.map((item) => ({ item, handle: add?.(object, item as I) }));
156
+ state.snapshot = structuredClone(items);
157
+
158
+ return [prop];
159
+ },
160
+ };
161
+ };
162
+
163
+ const flushDeferred = <P extends GObject.Object, V>(
164
+ object: GObject.Object,
165
+ context: unknown,
166
+ prop: string,
167
+ canApply: CanApply<P, V> | undefined,
168
+ ): void => {
169
+ const state = context as DeferredState;
170
+
171
+ if (!state.present || Object.is(state.applied, state.desired)) {
172
+ return;
173
+ }
174
+
175
+ if (canApply !== undefined && !canApply(object as P, state.desired as V)) {
176
+ return;
177
+ }
178
+
179
+ Reflect.set(object, prop, state.desired);
180
+ state.applied = state.desired;
181
+ };
182
+
183
+ /** Builds a behavior for a prop applied after the surrounding commit, deferred until `canApply` returns true. */
184
+ const deferred = <P extends GObject.Object, V>(
185
+ prop: string,
186
+ canApply?: CanApply<P, V>,
187
+ ): ElementBehavior<P> => ({
188
+ deferred: [prop],
189
+ createContext: (): DeferredState => ({ desired: undefined, present: false, applied: undefined }),
190
+ update: (_object, _prev, next, context) => {
191
+ const state = context as DeferredState;
192
+ state.desired = next[prop];
193
+ state.present = next[prop] !== undefined;
194
+
195
+ return [prop];
196
+ },
197
+ flush: (object, context) => {
198
+ flushDeferred(object, context, prop, canApply);
199
+ },
200
+ });
201
+
202
+ /** Builds a behavior for a text prop kept in controlled-input sync: set when provided, never reset. */
203
+ const controlledText = (prop: string): ElementBehavior =>
204
+ value(prop, (object, next) => {
205
+ if (!hasSameText(object, prop, next)) {
206
+ Reflect.set(object, prop, next);
207
+ }
208
+ });
209
+
210
+ /** Behavior for a container that installs its single child with `setChild`. */
211
+ const childSetterSlot = <P extends ChildSetter>(): ElementBehavior<P> =>
212
+ slot<P, Gtk.Widget>("children", "GtkWidget", {
213
+ attach: (parent, child) => {
214
+ parent.setChild(child);
215
+ },
216
+ detach: (parent) => {
217
+ parent.setChild(null);
218
+ },
219
+ });
220
+
221
+ /** Behavior for a container that installs its single child with `setContent`. */
222
+ const contentSetterSlot = <P extends ContentSetter<C>, C extends Gtk.Widget = Gtk.Widget>(
223
+ childType = "GtkWidget",
224
+ ): ElementBehavior<P> =>
225
+ slot<P, C>("children", childType, {
226
+ attach: (parent, child) => {
227
+ parent.setContent(child);
228
+ },
229
+ detach: (parent) => {
230
+ parent.setContent(null);
231
+ },
232
+ });
233
+
234
+ /** Behavior for a `GtkBox`-style container that orders children by sibling. */
235
+ const boxSlot = <P extends BoxLike>(): ElementBehavior<P> =>
236
+ slot<P, Gtk.Widget>("children", "GtkWidget", {
237
+ attach: (box, child, info) => box.insertChildAfter(child, info.sibling as Gtk.Widget | null),
238
+ detach: (box, child) => {
239
+ box.remove(child);
240
+ },
241
+ reorder: (box, child, info) => {
242
+ box.reorderChildAfter(child, info.sibling as Gtk.Widget | null);
243
+ },
244
+ });
245
+
246
+ /** Behavior for a container whose children are added and removed by a pair of methods. */
247
+ const addRemoveSlot = <C extends GObject.Object, P extends GObject.Object>(
248
+ slotName: string,
249
+ childType: string,
250
+ add: (parent: P, child: C) => unknown,
251
+ remove: (parent: P, child: C) => void,
252
+ ): ElementBehavior => slot<P, C>(slotName, childType, { attach: add, detach: remove });
253
+
254
+ /** Behavior for a `children` slot whose attach call returns the page object the container adopts. */
255
+ const adoptedChildrenSlot = <P extends GObject.Object, C extends GObject.Object>(
256
+ childType: string,
257
+ add: (parent: P, item: C) => unknown,
258
+ remove: (parent: P, item: C) => void,
259
+ ): ElementBehavior => addRemoveSlot<C, P>("children", childType, add, remove);
260
+
261
+ const wrappedRow = <W extends Gtk.Widget>(
262
+ Wrapper: new (props: Props) => W,
263
+ setChild: (wrapper: W, inner: Gtk.Widget) => void,
264
+ rows: RowCache,
265
+ child: Gtk.Widget,
266
+ ): Gtk.Widget => {
267
+ if (child instanceof Wrapper) {
268
+ return child;
269
+ }
270
+
271
+ const existing = rows.get(child);
272
+
273
+ if (existing !== undefined) {
274
+ return existing;
275
+ }
276
+
277
+ const wrapper = new Wrapper({});
278
+ setChild(wrapper, child);
279
+ rows.set(child, wrapper);
280
+
281
+ return wrapper;
282
+ };
283
+
284
+ const removeWrappedRow = (
285
+ Wrapper: new (props: Props) => Gtk.Widget,
286
+ parent: IndexedInserter,
287
+ child: Gtk.Widget,
288
+ rows: RowCache,
289
+ ): void => {
290
+ const row = child instanceof Wrapper ? child : rows.get(child);
291
+
292
+ if (row !== undefined) {
293
+ parent.remove(row);
294
+ }
295
+ };
296
+
297
+ /** Behavior for an index-placed container that wraps each child in `Wrapper` before adding it. */
298
+ const wrappingIndexedSlot = <W extends Gtk.Widget, P extends IndexedInserter>(
299
+ Wrapper: new (props: Props) => W,
300
+ setChild: (wrapper: W, inner: Gtk.Widget) => void,
301
+ ): ElementBehavior<P> => ({
302
+ ...slot<P, Gtk.Widget>("children", "GtkWidget", {
303
+ attach: (parent, child, info) =>
304
+ parent.insert(wrappedRow(Wrapper, setChild, info.context as RowCache, child), info.index),
305
+ detach: (parent, child, info) => {
306
+ removeWrappedRow(Wrapper, parent, child, info.context as RowCache);
307
+ },
308
+ }),
309
+ createContext: (): RowCache => new WeakMap(),
310
+ });
311
+
312
+ export {
313
+ slot,
314
+ value,
315
+ list,
316
+ deferred,
317
+ controlledText,
318
+ childSetterSlot,
319
+ contentSetterSlot,
320
+ boxSlot,
321
+ addRemoveSlot,
322
+ adoptedChildrenSlot,
323
+ wrappingIndexedSlot,
324
+ };