@react-x11/components 0.2.1 → 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 (137) hide show
  1. package/README.md +6 -0
  2. package/dist/index.d.ts +4 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +5 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal/heights.d.ts +26 -1
  7. package/dist/internal/heights.d.ts.map +1 -1
  8. package/dist/internal/heights.js +57 -6
  9. package/dist/internal/heights.js.map +1 -1
  10. package/dist/internal/scroll.d.ts +11 -0
  11. package/dist/internal/scroll.d.ts.map +1 -1
  12. package/dist/internal/scroll.js +43 -3
  13. package/dist/internal/scroll.js.map +1 -1
  14. package/dist/internal/timers.d.ts +8 -0
  15. package/dist/internal/timers.d.ts.map +1 -1
  16. package/dist/internal/timers.js +10 -0
  17. package/dist/internal/timers.js.map +1 -1
  18. package/dist/internal/window.d.ts +161 -0
  19. package/dist/internal/window.d.ts.map +1 -0
  20. package/dist/internal/window.js +417 -0
  21. package/dist/internal/window.js.map +1 -0
  22. package/dist/qml/containers.d.ts +3 -0
  23. package/dist/qml/containers.d.ts.map +1 -0
  24. package/dist/qml/containers.js +331 -0
  25. package/dist/qml/containers.js.map +1 -0
  26. package/dist/qml/controls.d.ts +12 -0
  27. package/dist/qml/controls.d.ts.map +1 -0
  28. package/dist/qml/controls.js +21 -0
  29. package/dist/qml/controls.js.map +1 -0
  30. package/dist/qml/globals.d.ts +6 -0
  31. package/dist/qml/globals.d.ts.map +1 -0
  32. package/dist/qml/globals.js +25 -0
  33. package/dist/qml/globals.js.map +1 -0
  34. package/dist/qml/index.d.ts +15 -0
  35. package/dist/qml/index.d.ts.map +1 -0
  36. package/dist/qml/index.js +25 -0
  37. package/dist/qml/index.js.map +1 -0
  38. package/dist/qml/interaction.d.ts +12 -0
  39. package/dist/qml/interaction.d.ts.map +1 -0
  40. package/dist/qml/interaction.js +246 -0
  41. package/dist/qml/interaction.js.map +1 -0
  42. package/dist/qml/ir.d.ts +106 -0
  43. package/dist/qml/ir.d.ts.map +1 -0
  44. package/dist/qml/ir.js +7 -0
  45. package/dist/qml/ir.js.map +1 -0
  46. package/dist/qml/layouts.d.ts +3 -0
  47. package/dist/qml/layouts.d.ts.map +1 -0
  48. package/dist/qml/layouts.js +122 -0
  49. package/dist/qml/layouts.js.map +1 -0
  50. package/dist/qml/models.d.ts +14 -0
  51. package/dist/qml/models.d.ts.map +1 -0
  52. package/dist/qml/models.js +80 -0
  53. package/dist/qml/models.js.map +1 -0
  54. package/dist/qml/objects.d.ts +228 -0
  55. package/dist/qml/objects.d.ts.map +1 -0
  56. package/dist/qml/objects.js +1103 -0
  57. package/dist/qml/objects.js.map +1 -0
  58. package/dist/qml/parse.d.ts +10 -0
  59. package/dist/qml/parse.d.ts.map +1 -0
  60. package/dist/qml/parse.js +607 -0
  61. package/dist/qml/parse.js.map +1 -0
  62. package/dist/qml/qtquick.d.ts +7 -0
  63. package/dist/qml/qtquick.d.ts.map +1 -0
  64. package/dist/qml/qtquick.js +347 -0
  65. package/dist/qml/qtquick.js.map +1 -0
  66. package/dist/qml/react.d.ts +83 -0
  67. package/dist/qml/react.d.ts.map +1 -0
  68. package/dist/qml/react.js +323 -0
  69. package/dist/qml/react.js.map +1 -0
  70. package/dist/qml/resolver.d.ts +38 -0
  71. package/dist/qml/resolver.d.ts.map +1 -0
  72. package/dist/qml/resolver.js +64 -0
  73. package/dist/qml/resolver.js.map +1 -0
  74. package/dist/qml/slots.d.ts +65 -0
  75. package/dist/qml/slots.d.ts.map +1 -0
  76. package/dist/qml/slots.js +230 -0
  77. package/dist/qml/slots.js.map +1 -0
  78. package/dist/qml/states.d.ts +5 -0
  79. package/dist/qml/states.d.ts.map +1 -0
  80. package/dist/qml/states.js +276 -0
  81. package/dist/qml/states.js.map +1 -0
  82. package/dist/qml/view-utils.d.ts +42 -0
  83. package/dist/qml/view-utils.d.ts.map +1 -0
  84. package/dist/qml/view-utils.js +121 -0
  85. package/dist/qml/view-utils.js.map +1 -0
  86. package/dist/table/index.d.ts +73 -2
  87. package/dist/table/index.d.ts.map +1 -1
  88. package/dist/table/index.js +405 -161
  89. package/dist/table/index.js.map +1 -1
  90. package/dist/tabs/hx.d.ts +18 -0
  91. package/dist/tabs/hx.d.ts.map +1 -0
  92. package/dist/tabs/hx.js +10 -0
  93. package/dist/tabs/hx.js.map +1 -0
  94. package/dist/tabs/index.d.ts +143 -0
  95. package/dist/tabs/index.d.ts.map +1 -0
  96. package/dist/tabs/index.js +602 -0
  97. package/dist/tabs/index.js.map +1 -0
  98. package/dist/terminal/vt/index.d.ts +1 -1
  99. package/dist/terminal/vt/index.d.ts.map +1 -1
  100. package/dist/terminal/vt/index.js +3 -3
  101. package/dist/terminal/vt/index.js.map +1 -1
  102. package/dist/terminal/vt/pty.d.ts +16 -0
  103. package/dist/terminal/vt/pty.d.ts.map +1 -1
  104. package/dist/terminal/vt/pty.js +186 -0
  105. package/dist/terminal/vt/pty.js.map +1 -1
  106. package/dist/tree/index.d.ts +65 -5
  107. package/dist/tree/index.d.ts.map +1 -1
  108. package/dist/tree/index.js +446 -189
  109. package/dist/tree/index.js.map +1 -1
  110. package/package.json +11 -1
  111. package/src/index.ts +44 -0
  112. package/src/internal/heights.ts +57 -6
  113. package/src/internal/scroll.ts +59 -3
  114. package/src/internal/timers.ts +13 -0
  115. package/src/internal/window.ts +570 -0
  116. package/src/qml/containers.tsx +380 -0
  117. package/src/qml/controls.tsx +47 -0
  118. package/src/qml/globals.ts +33 -0
  119. package/src/qml/index.ts +67 -0
  120. package/src/qml/interaction.tsx +317 -0
  121. package/src/qml/ir.ts +80 -0
  122. package/src/qml/layouts.tsx +141 -0
  123. package/src/qml/models.ts +101 -0
  124. package/src/qml/objects.ts +1431 -0
  125. package/src/qml/parse.ts +624 -0
  126. package/src/qml/qtquick.tsx +413 -0
  127. package/src/qml/react.tsx +454 -0
  128. package/src/qml/resolver.ts +99 -0
  129. package/src/qml/slots.ts +249 -0
  130. package/src/qml/states.ts +318 -0
  131. package/src/qml/view-utils.ts +140 -0
  132. package/src/table/index.ts +599 -178
  133. package/src/tabs/hx.ts +31 -0
  134. package/src/tabs/index.ts +850 -0
  135. package/src/terminal/vt/index.ts +4 -2
  136. package/src/terminal/vt/pty.ts +243 -0
  137. package/src/tree/index.ts +687 -260
@@ -0,0 +1,1103 @@
1
+ // The QML object model: documents → live instances wired into the slot
2
+ // graph.
3
+ //
4
+ // Scope rules follow Qt's documented order (ids outrank the scope object's
5
+ // own properties; the component root is consulted before context
6
+ // properties; unresolved names fall through `with` to the JS globals, which
7
+ // is how Math, JSON and console work without us lifting a finger).
8
+ //
9
+ // Expressions compile once per document via `new Function` and are shared
10
+ // by every instance (a Repeater with 1000 delegates compiles each binding
11
+ // once).
12
+ //
13
+ // This module is deliberately renderer-free: no react-x11, and React only
14
+ // as a type (the `view` field a registered type may carry). It is the half
15
+ // that a future compiler shares with the interpreter.
16
+ import { parseQml } from './parse.js';
17
+ import { Slot, flushBindings } from './slots.js';
18
+ import { warn } from './globals.js';
19
+ // --- type registry ---------------------------------------------------------
20
+ const modules = new Map();
21
+ export function registerQmlModule(name, { types }) {
22
+ const mod = modules.get(name) ?? { types: new Map() };
23
+ modules.set(name, mod); // visible before resolution: types extend siblings
24
+ for (const [typeName, def] of Object.entries(types)) {
25
+ mod.types.set(typeName, resolveTypeInfo(typeName, def));
26
+ }
27
+ }
28
+ function resolveTypeInfo(name, def) {
29
+ let base = null;
30
+ if (def.extends) {
31
+ for (const mod of modules.values()) {
32
+ const hit = mod.types.get(def.extends);
33
+ if (hit)
34
+ base = hit;
35
+ }
36
+ if (!base)
37
+ throw new Error(`QML type ${name} extends unknown ${def.extends}`);
38
+ }
39
+ return {
40
+ name,
41
+ base,
42
+ properties: { ...base?.properties, ...def.properties },
43
+ signals: { ...base?.signals, ...def.signals },
44
+ enums: { ...base?.enums, ...def.enums },
45
+ nonVisual: def.nonVisual ?? base?.nonVisual ?? false,
46
+ capture: def.capture ?? base?.capture ?? null,
47
+ dynamicProperties: def.dynamicProperties ?? base?.dynamicProperties ?? false,
48
+ deferHandlers: def.deferHandlers ?? base?.deferHandlers ?? false,
49
+ managesChildLayout: def.managesChildLayout ?? base?.managesChildLayout ?? false,
50
+ init: def.init ?? base?.init ?? null,
51
+ dispose: def.dispose ?? base?.dispose ?? null,
52
+ onStructure: def.onStructure ?? base?.onStructure ?? null,
53
+ view: def.view ?? base?.view ?? null,
54
+ };
55
+ }
56
+ export function lookupType(doc, typeName) {
57
+ // `alias.Type` from `import … as alias`
58
+ let name = typeName;
59
+ let onlyModule = null;
60
+ const dot = typeName.indexOf('.');
61
+ if (dot !== -1) {
62
+ const alias = typeName.slice(0, dot);
63
+ const imp = doc.imports.find((i) => i.alias === alias);
64
+ if (imp?.module) {
65
+ onlyModule = imp.module;
66
+ name = typeName.slice(dot + 1);
67
+ }
68
+ }
69
+ for (const imp of doc.imports) {
70
+ if (!imp.module)
71
+ continue;
72
+ if (onlyModule && imp.module !== onlyModule)
73
+ continue;
74
+ const info = modules.get(imp.module)?.types.get(name);
75
+ if (info)
76
+ return info;
77
+ }
78
+ const known = [...modules.keys()].join(', ');
79
+ const dirNote = doc.load
80
+ ? ` No <${typeName}>.qml was found beside the document or under its ` +
81
+ `quoted imports either.`
82
+ : ` Pass a resolver (createFileResolver) to also load <Name>.qml files.`;
83
+ throw new Error(`Unknown QML type '${typeName}' in ${doc.fileName}. Imported modules ` +
84
+ `were searched; registered modules: ${known}. Register your own with ` +
85
+ `registerQmlModule(module, { types }).` +
86
+ dirNote);
87
+ }
88
+ // Cached per (dir, name). Entries revalidate once per root instantiation
89
+ // (the generation bump below): a 1000-delegate ListView reads its component
90
+ // file once, and a hot reload of the root re-reads sources so edited
91
+ // siblings are picked up.
92
+ const fileCache = new Map();
93
+ let loadGeneration = 0;
94
+ function loadFileComponent(load, dir, name) {
95
+ const key = `${dir}\u0000${name}`;
96
+ const cached = fileCache.get(key);
97
+ if (cached && cached.gen === loadGeneration)
98
+ return cached.doc;
99
+ const hit = load.resolver.load(dir, name);
100
+ if (!hit) {
101
+ fileCache.set(key, { doc: null, source: null, gen: loadGeneration });
102
+ return null;
103
+ }
104
+ if (cached && cached.doc && cached.source === hit.source) {
105
+ cached.gen = loadGeneration; // unchanged: keep the parsed document
106
+ return cached.doc;
107
+ }
108
+ const compDoc = parseQml(hit.source, { fileName: hit.fileName });
109
+ compDoc.load = { resolver: load.resolver, dir };
110
+ fileCache.set(key, { doc: compDoc, source: hit.source, gen: loadGeneration });
111
+ return compDoc;
112
+ }
113
+ /** Qt's precedence: the implicit same-directory import first (a local
114
+ * `Button.qml` shadows a module's `Button`), then quoted-path imports,
115
+ * then registered modules. */
116
+ function resolveTypeRef(doc, typeName) {
117
+ const load = doc.load;
118
+ if (load && !typeName.includes('.')) {
119
+ const implicit = loadFileComponent(load, load.dir, typeName);
120
+ if (implicit)
121
+ return { fileDoc: implicit };
122
+ for (const imp of doc.imports) {
123
+ if (!imp.path)
124
+ continue;
125
+ const dir = load.resolver.join(load.dir, imp.path);
126
+ const fromPath = loadFileComponent(load, dir, typeName);
127
+ if (fromPath)
128
+ return { fileDoc: fromPath };
129
+ }
130
+ }
131
+ return lookupType(doc, typeName);
132
+ }
133
+ /** `Loader { source: "widgets/Meter.qml" }` — a relative path resolved
134
+ * against the document that names it. Null without a resolver or when the
135
+ * file is absent. */
136
+ export function resolveComponentByPath(fromDoc, path) {
137
+ const load = fromDoc.load;
138
+ if (!load || !/\.qml$/i.test(path))
139
+ return null;
140
+ const parts = path.replace(/\.qml$/i, '').split('/');
141
+ const name = parts.pop();
142
+ if (!name)
143
+ return null;
144
+ const dir = parts.length
145
+ ? load.resolver.join(load.dir, parts.join('/'))
146
+ : load.dir;
147
+ return loadFileComponent(load, dir, name);
148
+ }
149
+ // Instantiation-time cycle guard: A.qml using B.qml using A.qml would
150
+ // recurse forever in createTree; the parse-level cache cannot see it
151
+ // because each document parses fine on its own.
152
+ const instantiating = [];
153
+ function topContextOf(ctx) {
154
+ let top = ctx;
155
+ while (top.parent)
156
+ top = top.parent;
157
+ return top;
158
+ }
159
+ const compiledCache = new WeakMap();
160
+ function compileExpr(node, doc) {
161
+ let fn = compiledCache.get(node);
162
+ if (!fn) {
163
+ try {
164
+ fn = new Function('$scope', `with($scope){ return (${node.src}\n) }`);
165
+ }
166
+ catch (e) {
167
+ throw new Error(`QML: bad expression in ${doc.fileName}: \`${node.src}\` (${message(e)})`);
168
+ }
169
+ compiledCache.set(node, fn);
170
+ }
171
+ return fn;
172
+ }
173
+ function compileHandler(node, params, doc) {
174
+ let fn = compiledCache.get(node);
175
+ if (!fn) {
176
+ const body = node.kind === 'block' ? node.src : `return (${node.src}\n)`;
177
+ try {
178
+ fn = new Function('$scope', `with($scope){ return function(${params.join(', ')}){ ${body} \n} }`);
179
+ }
180
+ catch (e) {
181
+ throw new Error(`QML: bad handler in ${doc.fileName}: \`${node.src}\` (${message(e)})`);
182
+ }
183
+ compiledCache.set(node, fn);
184
+ }
185
+ return fn;
186
+ }
187
+ const message = (e) => e instanceof Error ? e.message : String(e);
188
+ // --- contexts --------------------------------------------------------------
189
+ export class Context {
190
+ parent;
191
+ doc;
192
+ ids = new Map();
193
+ extras = new Map(); // context properties
194
+ root = null;
195
+ constructor(parent, doc) {
196
+ this.parent = parent;
197
+ this.doc = doc;
198
+ }
199
+ lookupId(name) {
200
+ for (let c = this; c; c = c.parent) {
201
+ const inst = c.ids.get(name);
202
+ if (inst)
203
+ return inst;
204
+ }
205
+ return null;
206
+ }
207
+ lookupExtra(name) {
208
+ for (let c = this; c; c = c.parent) {
209
+ const slot = c.extras.get(name);
210
+ if (slot)
211
+ return slot;
212
+ }
213
+ return null;
214
+ }
215
+ }
216
+ const QtBindingMark = Symbol('Qt.binding');
217
+ export const Qt = {
218
+ rgba: (r, g, b, a = 1) => `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${a})`,
219
+ hsla: (h, s, l, a = 1) => `hsla(${Math.round(h * 360)}, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%, ${a})`,
220
+ point: (x, y) => ({ x, y }),
221
+ size: (width, height) => ({ width, height }),
222
+ rect: (x, y, width, height) => ({ x, y, width, height }),
223
+ binding: (fn) => ({ [QtBindingMark]: fn }),
224
+ qsTr: (s) => s,
225
+ };
226
+ // Parameterized by (ctx, doc) rather than reading the instance's own: a
227
+ // file-backed instance evaluates its use-site members in the *site's*
228
+ // scope — same object, different name resolution.
229
+ function makeScopeProxy(inst, ctx, doc) {
230
+ const scopeHas = (name) => {
231
+ if (name === 'Qt' || name === 'parent' || name === 'this')
232
+ return true;
233
+ if (ctx.lookupId(name))
234
+ return true;
235
+ if (inst.resolveMember(name) !== undefined)
236
+ return true;
237
+ const root = ctx.root;
238
+ if (root && root !== inst && root.resolveMember(name) !== undefined)
239
+ return true;
240
+ if (ctx.lookupExtra(name))
241
+ return true;
242
+ if (enumHolder(doc, name))
243
+ return true;
244
+ return false; // fall through `with` to the real JS globals
245
+ };
246
+ return new Proxy(Object.create(null), {
247
+ has: (_, name) => (typeof name === 'string' ? scopeHas(name) : false),
248
+ get: (_, name) => {
249
+ if (typeof name !== 'string')
250
+ return undefined;
251
+ if (name === 'Qt')
252
+ return Qt;
253
+ if (name === 'parent')
254
+ return inst.parentInst?.facade ?? null;
255
+ const idHit = ctx.lookupId(name);
256
+ if (idHit)
257
+ return idHit.facade;
258
+ let v = inst.getMember(name);
259
+ if (v !== undefined)
260
+ return v;
261
+ const root = ctx.root;
262
+ if (root && root !== inst) {
263
+ v = root.getMember(name);
264
+ if (v !== undefined)
265
+ return v;
266
+ }
267
+ const extra = ctx.lookupExtra(name);
268
+ if (extra)
269
+ return extra.get();
270
+ const holder = enumHolder(doc, name);
271
+ if (holder)
272
+ return holder;
273
+ return undefined;
274
+ },
275
+ set: (_, name, value) => {
276
+ if (typeof name !== 'string')
277
+ return false;
278
+ // Assignment in handlers: prefer the object that owns the property.
279
+ if (inst.slots.has(name)) {
280
+ inst.setMember(name, value);
281
+ return true;
282
+ }
283
+ const root = ctx.root;
284
+ if (root && root !== inst && root.slots.has(name)) {
285
+ root.setMember(name, value);
286
+ return true;
287
+ }
288
+ throw new Error(`QML: cannot assign unknown name '${name}'`);
289
+ },
290
+ });
291
+ }
292
+ /** `Text.AlignHCenter` — uppercase names resolve to a type's enums.
293
+ * Registry-only on purpose: this runs for every capitalized name any
294
+ * expression mentions, and must never cost a resolver probe. */
295
+ function enumHolder(doc, name) {
296
+ if (!/^[A-Z]/.test(name))
297
+ return null;
298
+ try {
299
+ const info = lookupType(doc, name);
300
+ return Object.keys(info.enums).length ? info.enums : null;
301
+ }
302
+ catch {
303
+ return null;
304
+ }
305
+ }
306
+ // --- instances -------------------------------------------------------------
307
+ let nextInstanceId = 1;
308
+ export class QmlInstance {
309
+ uid;
310
+ typeInfo;
311
+ doc;
312
+ context;
313
+ parentInst;
314
+ id = null;
315
+ ir = null;
316
+ slots = new Map();
317
+ methods = new Map();
318
+ signalParams = new Map();
319
+ signalSubs = new Map();
320
+ /** Effective visual children — Repeater splices instances in here. */
321
+ children = [];
322
+ /** Captured object templates (`delegate`, `sourceComponent`, …). */
323
+ templates = new Map();
324
+ /** For a file-backed component instance: the use-site members, applied
325
+ * in the site's own scope during activation. */
326
+ site = null;
327
+ valueSources = [];
328
+ /** `Keys.onPressed` and friends: attached handlers by dotted name. */
329
+ attachedHandlers = new Map();
330
+ /** `Connections`: raw `onX` members, wired by the type's init. */
331
+ deferredHandlers = [];
332
+ onDestruction = [];
333
+ anchorLines = new Map();
334
+ version = 0;
335
+ structVersion = 0;
336
+ listeners = new Set();
337
+ destroyed = false;
338
+ /** Per-type scratch: timers, behaviors, repeater items, host node… */
339
+ state = {};
340
+ scope;
341
+ facade;
342
+ _activated = false;
343
+ constructor(typeInfo, doc, context, parentInst) {
344
+ this.uid = nextInstanceId++;
345
+ this.typeInfo = typeInfo;
346
+ this.doc = doc;
347
+ this.context = context;
348
+ this.parentInst = parentInst;
349
+ this.scope = makeScopeProxy(this, context, doc);
350
+ this.facade = makeFacade(this);
351
+ for (const [name, decl] of Object.entries(typeInfo.properties)) {
352
+ this.addSlot(name, decl.default);
353
+ }
354
+ for (const [name, params] of Object.entries(typeInfo.signals)) {
355
+ this.signalParams.set(name, params);
356
+ }
357
+ }
358
+ addSlot(name, value) {
359
+ const slot = new Slot(this, name, value);
360
+ this.slots.set(name, slot);
361
+ return slot;
362
+ }
363
+ slot(name) {
364
+ const s = this.slots.get(name);
365
+ if (!s)
366
+ throw new Error(`QML: ${this.typeInfo.name} has no property '${name}' (${this.doc.fileName})`);
367
+ return s;
368
+ }
369
+ /** undefined ⇒ not a member (so scope lookup continues up the chain). */
370
+ resolveMember(name) {
371
+ if (this.slots.has(name))
372
+ return this.slots.get(name);
373
+ if (this.methods.has(name))
374
+ return this.methods.get(name);
375
+ if (this.signalParams.has(name))
376
+ return name;
377
+ return undefined;
378
+ }
379
+ getMember(name) {
380
+ const slot = this.slots.get(name);
381
+ if (slot) {
382
+ const v = slot.get();
383
+ return v === undefined ? null : v; // undefined would break the walk
384
+ }
385
+ if (this.methods.has(name))
386
+ return this.methods.get(name);
387
+ if (this.signalParams.has(name)) {
388
+ return (...args) => this.emit(name, ...args);
389
+ }
390
+ return undefined;
391
+ }
392
+ setMember(name, value) {
393
+ if (value && typeof value === 'object' && QtBindingMark in value) {
394
+ const fn = value[QtBindingMark];
395
+ this.slot(name).setBinding(() => fn.call(this.facade));
396
+ return;
397
+ }
398
+ this.slot(name).set(value);
399
+ }
400
+ emit(name, ...args) {
401
+ const subs = this.signalSubs.get(name);
402
+ if (subs)
403
+ for (const fn of [...subs])
404
+ fn(...args);
405
+ flushBindings();
406
+ }
407
+ onSignal(name, fn) {
408
+ if (!this.signalParams.has(name) && !this.slots.has(stripChanged(name)))
409
+ throw new Error(`QML: ${this.typeInfo.name} has no signal '${name}'`);
410
+ let set = this.signalSubs.get(name);
411
+ if (!set)
412
+ this.signalSubs.set(name, (set = new Set()));
413
+ set.add(fn);
414
+ return () => set.delete(fn);
415
+ }
416
+ anchorLine(edge) {
417
+ let line = this.anchorLines.get(edge);
418
+ if (!line)
419
+ this.anchorLines.set(edge, (line = { inst: this, edge }));
420
+ return line;
421
+ }
422
+ /** React bridge: any slot change bumps the version; one subscription
423
+ * per object. */
424
+ _changed() {
425
+ this.version++;
426
+ for (const l of [...this.listeners])
427
+ l();
428
+ }
429
+ _structChanged() {
430
+ this.structVersion++;
431
+ this.typeInfo.onStructure?.(this); // positioners re-pin children
432
+ this._changed();
433
+ }
434
+ subscribe(fn) {
435
+ this.listeners.add(fn);
436
+ return () => this.listeners.delete(fn);
437
+ }
438
+ visualChildren() {
439
+ return this.children.filter((c) => !c.typeInfo.nonVisual);
440
+ }
441
+ destroy() {
442
+ if (this.destroyed)
443
+ return;
444
+ this.destroyed = true;
445
+ for (const fn of this.onDestruction)
446
+ fn();
447
+ for (const child of this.children)
448
+ child.destroy();
449
+ for (const src of this.valueSources)
450
+ src.destroy();
451
+ this.typeInfo.dispose?.(this);
452
+ for (const slot of this.slots.values())
453
+ slot.destroy();
454
+ this.listeners.clear();
455
+ this.signalSubs.clear();
456
+ }
457
+ }
458
+ const stripChanged = (n) => /^on[A-Z].*Changed$/.test(n)
459
+ ? n.slice(2, -7)[0].toLowerCase() + n.slice(3, -7)
460
+ : '';
461
+ // --- facade ----------------------------------------------------------------
462
+ const FACADE = Symbol('qml-instance');
463
+ /** The instance behind a facade, or null for anything else. */
464
+ export const instanceOf = (facade) => facade && typeof facade === 'object'
465
+ ? (facade[FACADE] ??
466
+ null)
467
+ : null;
468
+ const ANCHOR_EDGES = new Set([
469
+ 'left',
470
+ 'right',
471
+ 'top',
472
+ 'bottom',
473
+ 'horizontalCenter',
474
+ 'verticalCenter',
475
+ ]);
476
+ function makeFacade(inst) {
477
+ return new Proxy(Object.create(null), {
478
+ has: (_, name) => typeof name === 'string' &&
479
+ (name === 'parent' ||
480
+ ANCHOR_EDGES.has(name) ||
481
+ inst.resolveMember(name) !== undefined),
482
+ get: (_, name) => {
483
+ if (name === FACADE)
484
+ return inst;
485
+ if (typeof name !== 'string')
486
+ return undefined;
487
+ if (name === 'parent')
488
+ return inst.parentInst?.facade ?? null;
489
+ if (ANCHOR_EDGES.has(name) && !inst.slots.has(name))
490
+ return inst.anchorLine(name);
491
+ return inst.getMember(name);
492
+ },
493
+ set: (_, name, value) => {
494
+ if (typeof name !== 'string')
495
+ return false;
496
+ inst.setMember(name, value);
497
+ return true;
498
+ },
499
+ });
500
+ }
501
+ // --- instantiation ---------------------------------------------------------
502
+ let activating = false;
503
+ export function instantiateDocument(doc, { context = null, extras = null, resolver = null, } = {}) {
504
+ if (resolver) {
505
+ doc.load = { resolver, dir: resolver.rootDir };
506
+ loadGeneration++; // revalidate cached component sources on each mount
507
+ }
508
+ const ctx = new Context(context, doc);
509
+ if (extras) {
510
+ for (const [k, v] of Object.entries(extras)) {
511
+ ctx.extras.set(k, new Slot(null, k, v));
512
+ }
513
+ }
514
+ const completedQueue = [];
515
+ const root = createTree(doc.root, doc, ctx, null);
516
+ ctx.root = root;
517
+ const wasActivating = activating;
518
+ activating = true;
519
+ try {
520
+ activate(root, completedQueue);
521
+ flushBindings();
522
+ }
523
+ finally {
524
+ activating = wasActivating;
525
+ }
526
+ retryFailedBindings(root);
527
+ for (const fn of completedQueue)
528
+ fn(); // leaf-first: queued on the way up
529
+ flushBindings();
530
+ return { root, context: ctx };
531
+ }
532
+ /**
533
+ * A delegate template instantiated into a child context (Repeater, Loader,
534
+ * ListView). `extras` become context properties (index, modelData, roles);
535
+ * ids inside the template scope to the child context while names from the
536
+ * enclosing document keep resolving through the chain.
537
+ */
538
+ export function instantiateTemplate(tpl, parentInst, extras = null) {
539
+ const ctx = new Context(tpl.context, tpl.doc);
540
+ ctx.root = tpl.context.root;
541
+ if (extras) {
542
+ for (const [k, v] of Object.entries(extras)) {
543
+ ctx.extras.set(k, new Slot(null, k, v));
544
+ }
545
+ }
546
+ const completedQueue = [];
547
+ const inst = createTree(tpl.object, tpl.doc, ctx, parentInst);
548
+ const wasActivating = activating;
549
+ activating = true;
550
+ try {
551
+ activate(inst, completedQueue);
552
+ flushBindings();
553
+ }
554
+ finally {
555
+ activating = wasActivating;
556
+ }
557
+ retryFailedBindings(inst);
558
+ for (const fn of completedQueue)
559
+ fn();
560
+ return { inst, context: ctx };
561
+ }
562
+ /**
563
+ * Instantiate a whole component document (Loader's `source` path). Its
564
+ * names resolve within its own file; only the top-level context — where
565
+ * `QmlView`'s context properties live — is shared through.
566
+ */
567
+ export function instantiateComponentDoc(compDoc, nearContext, parentInst) {
568
+ const ctx = new Context(topContextOf(nearContext), compDoc);
569
+ const completedQueue = [];
570
+ const inst = createTree(compDoc.root, compDoc, ctx, parentInst);
571
+ ctx.root = inst;
572
+ const wasActivating = activating;
573
+ activating = true;
574
+ try {
575
+ activate(inst, completedQueue);
576
+ flushBindings();
577
+ }
578
+ finally {
579
+ activating = wasActivating;
580
+ }
581
+ retryFailedBindings(inst);
582
+ for (const fn of completedQueue)
583
+ fn();
584
+ return { inst, context: ctx };
585
+ }
586
+ /**
587
+ * Hot reload: carry interactive state (slots assigned at runtime —
588
+ * toggles, counters, typed text) from the old tree onto the new one,
589
+ * matched by id + property name. Everything else re-derives from the
590
+ * edited source.
591
+ */
592
+ export function migrateUserState(oldContext, newContext) {
593
+ let carried = 0;
594
+ for (const [id, oldInst] of oldContext.ids) {
595
+ const newInst = newContext.ids.get(id);
596
+ if (!newInst)
597
+ continue;
598
+ for (const [name, oldSlot] of oldInst.slots) {
599
+ if (!oldSlot.userSet)
600
+ continue;
601
+ const newSlot = newInst.slots.get(name);
602
+ if (!newSlot)
603
+ continue;
604
+ newSlot.set(oldSlot.peek());
605
+ carried++;
606
+ }
607
+ }
608
+ flushBindings();
609
+ return carried;
610
+ }
611
+ /** A binding still failing after the graph settled deserves its warning —
612
+ * and a broken dependency read may have kept it from subscribing to
613
+ * anything that would re-run it, so run it once more by hand. */
614
+ function retryFailedBindings(inst) {
615
+ for (const child of inst.children)
616
+ retryFailedBindings(child);
617
+ for (const slot of inst.slots.values()) {
618
+ if (slot.hasBinding() && slot.lastError)
619
+ slot._reevaluate();
620
+ }
621
+ }
622
+ function createTree(objIr, doc, ctx, parentInst) {
623
+ const resolved = resolveTypeRef(doc, objIr.type);
624
+ if ('fileDoc' in resolved) {
625
+ // A file-backed component: instantiate its document's root in its own
626
+ // context (ids and root-props private to the file; only the top-level
627
+ // context — QmlView's context properties — is shared), then lay the
628
+ // use-site members over it. Site bindings evaluate in the *site's*
629
+ // scope during activation; instantiator bindings win, as in Qt.
630
+ const compDoc = resolved.fileDoc;
631
+ if (instantiating.includes(compDoc)) {
632
+ const chain = [...instantiating.map((d) => d.fileName), compDoc.fileName];
633
+ throw new Error(`QML: circular component reference: ${chain.join(' -> ')}`);
634
+ }
635
+ instantiating.push(compDoc);
636
+ let inst;
637
+ try {
638
+ const compCtx = new Context(topContextOf(ctx), compDoc);
639
+ inst = createTree(compDoc.root, compDoc, compCtx, parentInst);
640
+ compCtx.root = inst;
641
+ }
642
+ finally {
643
+ instantiating.pop();
644
+ }
645
+ if (objIr.id) {
646
+ inst.id = objIr.id;
647
+ ctx.ids.set(objIr.id, inst);
648
+ }
649
+ inst.site = { ir: objIr, doc, ctx };
650
+ collectStructural(inst, objIr, doc, ctx);
651
+ return inst;
652
+ }
653
+ const typeInfo = resolved;
654
+ const inst = new QmlInstance(typeInfo, doc, ctx, parentInst);
655
+ inst.ir = objIr;
656
+ // Qt's default: an unset width tracks implicitWidth (`widthValid`). Any
657
+ // later binding or assignment replaces this — binding-breaking gives the
658
+ // exact semantics with no bookkeeping.
659
+ if (inst.slots.has('width') && inst.slots.has('implicitWidth')) {
660
+ inst
661
+ .slot('width')
662
+ .setBinding(() => inst.slot('implicitWidth').get(), { isDefault: true });
663
+ inst
664
+ .slot('height')
665
+ .setBinding(() => inst.slot('implicitHeight').get(), { isDefault: true });
666
+ }
667
+ if (objIr.id) {
668
+ inst.id = objIr.id;
669
+ ctx.ids.set(objIr.id, inst);
670
+ }
671
+ collectStructural(inst, objIr, doc, ctx);
672
+ return inst;
673
+ }
674
+ /** The structural half of a member list: declarations, children, captured
675
+ * templates. Runs once for a plain instance; a file-backed instance runs
676
+ * it twice — the component document's pass happened inside its own
677
+ * createTree, and the use site's runs here with the site's doc and
678
+ * context, so site children and site delegates resolve site names. */
679
+ function collectStructural(inst, objIr, doc, ctx) {
680
+ const capture = inst.typeInfo.capture;
681
+ for (const m of objIr.members) {
682
+ if (m.kind === 'property') {
683
+ if (m.propType !== 'alias' && !inst.slots.has(m.name)) {
684
+ inst.addSlot(m.name, defaultForPropType(m.propType));
685
+ }
686
+ }
687
+ else if (m.kind === 'signal') {
688
+ inst.signalParams.set(m.name, m.params.map((p) => p.name));
689
+ }
690
+ else if (m.kind === 'object') {
691
+ if (capture) {
692
+ if (!inst.templates.has(capture))
693
+ inst.templates.set(capture, {
694
+ object: m.object,
695
+ doc,
696
+ context: ctx,
697
+ });
698
+ }
699
+ else {
700
+ inst.children.push(createTree(m.object, doc, ctx, inst));
701
+ }
702
+ }
703
+ else if (m.kind === 'binding' && m.value.kind === 'object') {
704
+ const name = m.path.join('.');
705
+ if (name === capture || m.value.object.type === 'Component') {
706
+ // `delegate: Text { … }`, `sourceComponent: Component { … }` —
707
+ // component-typed properties stay templates, instantiated on demand.
708
+ inst.templates.set(name, {
709
+ object: unwrapComponent(m.value.object),
710
+ doc,
711
+ context: ctx,
712
+ });
713
+ }
714
+ else {
715
+ // `model: ListModel { … }` — an object rvalue instantiates, and the
716
+ // property is assigned the reference (QML's rule).
717
+ const child = createTree(m.value.object, doc, ctx, inst);
718
+ inst.children.push(child);
719
+ (inst.state.objectAssigns ??= []).push([name, child]);
720
+ }
721
+ }
722
+ else if (m.kind === 'binding' && m.value.kind === 'array') {
723
+ // `states: [State { … }]` — templates too, under the property name.
724
+ const objects = m.value.items
725
+ .filter((it) => it.kind === 'object')
726
+ .map((it) => it.object);
727
+ if (objects.length === m.value.items.length && objects.length > 0) {
728
+ inst.state[`templates:${m.path.join('.')}`] = objects.map((object) => ({ object, doc, context: ctx }));
729
+ }
730
+ }
731
+ else if (m.kind === 'inline-component') {
732
+ throw new Error(`QML: inline components (component ${m.name}: …) are not there ` +
733
+ `yet; put the component in its own <Name>.qml next to this ` +
734
+ `document (resolved via the resolver seam), or define it in ` +
735
+ `JavaScript with registerQmlModule.`);
736
+ }
737
+ }
738
+ }
739
+ function defaultForPropType(propType) {
740
+ if (propType === 'int' || propType === 'real' || propType === 'double')
741
+ return 0;
742
+ if (propType === 'bool')
743
+ return false;
744
+ if (propType === 'string')
745
+ return '';
746
+ if (propType === 'list')
747
+ return [];
748
+ return undefined;
749
+ }
750
+ const unwrapComponent = (obj) => {
751
+ if (obj.type !== 'Component')
752
+ return obj;
753
+ const child = obj.members.find((m) => m.kind === 'object');
754
+ if (!child)
755
+ throw new Error('QML: Component {} needs exactly one child');
756
+ return child.object;
757
+ };
758
+ const ownEnv = (inst) => ({
759
+ doc: inst.doc,
760
+ ctx: inst.context,
761
+ scope: inst.scope,
762
+ facade: inst.facade,
763
+ });
764
+ function activate(inst, completedQueue) {
765
+ if (inst._activated)
766
+ return; // Repeater splices siblings mid-walk
767
+ inst._activated = true;
768
+ const objIr = inst.ir;
769
+ if (!objIr)
770
+ return;
771
+ for (const child of [...inst.children])
772
+ activate(child, completedQueue);
773
+ applyMembers(inst, objIr.members, ownEnv(inst), completedQueue);
774
+ if (inst.site) {
775
+ const site = inst.site;
776
+ applyMembers(inst, site.ir.members, {
777
+ doc: site.doc,
778
+ ctx: site.ctx,
779
+ scope: makeScopeProxy(inst, site.ctx, site.doc),
780
+ facade: inst.facade,
781
+ }, completedQueue);
782
+ }
783
+ // Object rvalues (`model: ListModel { … }`): the instance exists since
784
+ // createTree; hand the property its reference before the type's init
785
+ // reads it.
786
+ const objectAssigns = inst.state.objectAssigns;
787
+ if (objectAssigns) {
788
+ for (const [name, child] of objectAssigns) {
789
+ const slot = inst.slots.get(name) ??
790
+ (inst.typeInfo.dynamicProperties
791
+ ? inst.addSlot(name, undefined)
792
+ : inst.slot(name));
793
+ slot.assign(child.facade);
794
+ }
795
+ }
796
+ installAnchors(inst);
797
+ inst.typeInfo.init?.(inst);
798
+ }
799
+ function applyMembers(inst, members, env, completedQueue) {
800
+ const applyBinding = (path, value) => {
801
+ if (path.length === 1 && /^on[A-Z]/.test(path[0])) {
802
+ if (inst.typeInfo.deferHandlers) {
803
+ inst.deferredHandlers.push({ name: path[0], value });
804
+ return;
805
+ }
806
+ attachHandler(inst, path[0], value, env);
807
+ return;
808
+ }
809
+ if (/^[A-Z]/.test(path[0]) && path.length > 1) {
810
+ attachAttached(inst, path, value, env, completedQueue);
811
+ return;
812
+ }
813
+ const name = path.join('.'); // grouped props live as dotted slot names
814
+ if (value.kind === 'object')
815
+ return; // captured as a template already
816
+ if (value.kind === 'array')
817
+ return; // captured as templates already
818
+ let slot = inst.slots.get(name);
819
+ if (!slot) {
820
+ if (inst.typeInfo.dynamicProperties)
821
+ slot = inst.addSlot(name, undefined);
822
+ else
823
+ slot = inst.slot(name); // throws with the readable message
824
+ }
825
+ if (value.kind === 'block') {
826
+ // `width: { if (…) return a; return b }` — a script binding; QML
827
+ // re-evaluates these like any binding when dependencies change.
828
+ const h = compileHandler(value, [], env.doc)(env.scope);
829
+ installGuardedBinding(inst, slot, value, env.doc, name, () => h.call(env.facade));
830
+ return;
831
+ }
832
+ if (isLiteral(value.src)) {
833
+ slot.assign(evalLiteral(value.src));
834
+ return;
835
+ }
836
+ const fn = compileExpr(value, env.doc);
837
+ installGuardedBinding(inst, slot, value, env.doc, name, () => fn.call(env.facade, env.scope));
838
+ };
839
+ for (const m of members) {
840
+ if (m.kind === 'property' && m.value) {
841
+ if (m.propType === 'alias') {
842
+ installAlias(inst, m.name, m.value, env);
843
+ }
844
+ else {
845
+ applyBinding([m.name], m.value);
846
+ }
847
+ }
848
+ else if (m.kind === 'binding') {
849
+ applyBinding(m.path, m.value);
850
+ }
851
+ else if (m.kind === 'group') {
852
+ for (const b of m.bindings)
853
+ applyBinding(b.path, b.value);
854
+ }
855
+ else if (m.kind === 'function') {
856
+ const factory = compileHandler({ kind: 'block', src: m.body }, m.params, env.doc);
857
+ const f = factory(env.scope);
858
+ inst.methods.set(m.name, (...args) => f.call(env.facade, ...args));
859
+ }
860
+ else if (m.kind === 'value-source') {
861
+ installValueSource(inst, m, env);
862
+ }
863
+ }
864
+ }
865
+ function installGuardedBinding(inst, slot, value, doc, name, evalFn) {
866
+ slot.setBinding(() => {
867
+ try {
868
+ const v = evalFn();
869
+ slot.lastError = null;
870
+ return v;
871
+ }
872
+ catch (e) {
873
+ slot.lastError = e;
874
+ // First-pass evaluation order is unspecified (a child's binding can
875
+ // read a root property whose own binding has not run yet) — exactly
876
+ // as in Qt. Stay quiet during activation; instantiateDocument
877
+ // re-evaluates whatever is still broken once the graph settles, and
878
+ // *that* failure warns.
879
+ if (!activating)
880
+ reportBindingError(doc, value, name, e);
881
+ return slot.peek();
882
+ }
883
+ });
884
+ }
885
+ function isLiteral(src) {
886
+ return /^(-?\d+(\.\d+)?|true|false|null|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')$/.test(src.trim());
887
+ }
888
+ function evalLiteral(src) {
889
+ const t = src.trim();
890
+ if (t === 'true')
891
+ return true;
892
+ if (t === 'false')
893
+ return false;
894
+ if (t === 'null')
895
+ return null;
896
+ if (t[0] === '"' || t[0] === "'")
897
+ return t.slice(1, -1).replace(/\\(.)/g, '$1');
898
+ return Number(t);
899
+ }
900
+ const reportedBindingErrors = new Set();
901
+ function reportBindingError(doc, value, name, e) {
902
+ const key = `${doc.fileName}:${name}`;
903
+ if (reportedBindingErrors.has(key))
904
+ return;
905
+ reportedBindingErrors.add(key);
906
+ const src = 'src' in value ? value.src : '';
907
+ warn(`QML: binding for '${name}' in ${doc.fileName} threw: ${message(e)}\n \`${src}\``);
908
+ }
909
+ // `onFoo: expr-or-block` — but also the modern `onFoo: (args) => { … }`,
910
+ // where the RHS evaluates to the function to invoke with the signal's args.
911
+ const FN_VALUED = /^\s*(function\b|\([^)]*\)\s*=>|[A-Za-z_$][\w$]*\s*=>)/;
912
+ function makeHandlerFn(inst, value, params, env) {
913
+ if (value.kind === 'expr' && FN_VALUED.test(value.src)) {
914
+ const arrow = compileExpr(value, env.doc).call(env.facade, env.scope);
915
+ return (...args) => arrow.apply(env.facade, args);
916
+ }
917
+ if (value.kind !== 'expr' && value.kind !== 'block')
918
+ throw new Error(`QML: a handler cannot be an object (${env.doc.fileName})`);
919
+ const f = compileHandler(value, params, env.doc)(env.scope);
920
+ return (...args) => f.call(env.facade, ...args);
921
+ }
922
+ /** Compile a stored handler in an instance's scope — how `Connections`
923
+ * wires its deferred `onX` members to another object's signals. */
924
+ export function makeHandlerFor(inst, value, params) {
925
+ return makeHandlerFn(inst, value, params, ownEnv(inst));
926
+ }
927
+ function attachHandler(inst, onName, value, env) {
928
+ const base = onName[2].toLowerCase() + onName.slice(3);
929
+ // Property-change handler?
930
+ const changed = stripChanged(onName);
931
+ if (changed && inst.slots.has(changed)) {
932
+ const f = makeHandlerFn(inst, value, [], env);
933
+ inst.slots.get(changed)?.watch(() => f());
934
+ return;
935
+ }
936
+ const params = inst.signalParams.get(base);
937
+ if (!params)
938
+ throw new Error(`QML: '${onName}' in ${env.doc.fileName} matches no signal or property on ${inst.typeInfo.name}`);
939
+ inst.onSignal(base, makeHandlerFn(inst, value, params, env));
940
+ }
941
+ function attachAttached(inst, path, value, env, completedQueue) {
942
+ const ns = path[0];
943
+ const name = path.slice(1).join('.');
944
+ if (ns === 'Component' && name === 'onCompleted') {
945
+ const f = makeHandlerFn(inst, value, [], env);
946
+ completedQueue.push(() => f());
947
+ return;
948
+ }
949
+ if (ns === 'Component' && name === 'onDestruction') {
950
+ const f = makeHandlerFn(inst, value, [], env);
951
+ inst.onDestruction.push(() => f());
952
+ return;
953
+ }
954
+ if (/^on[A-Z]/.test(name)) {
955
+ // `Keys.onPressed`, `Keys.onReturnPressed`: stored; the view wires
956
+ // them to the focus/key system (qtquick.tsx).
957
+ const params = ['event'];
958
+ inst.attachedHandlers.set(`${ns}.${name}`, makeHandlerFn(inst, value, params, env));
959
+ return;
960
+ }
961
+ // `Layout.fillWidth` and friends — attached values, namespaced slots.
962
+ const slotName = `${ns}.${name}`;
963
+ const slot = inst.slots.get(slotName) ?? inst.addSlot(slotName, undefined);
964
+ if (value.kind === 'expr' && isLiteral(value.src)) {
965
+ slot.assign(evalLiteral(value.src));
966
+ return;
967
+ }
968
+ if (value.kind === 'expr') {
969
+ const fn = compileExpr(value, env.doc);
970
+ installGuardedBinding(inst, slot, value, env.doc, slotName, () => fn.call(env.facade, env.scope));
971
+ return;
972
+ }
973
+ if (value.kind === 'block') {
974
+ const h = compileHandler(value, [], env.doc)(env.scope);
975
+ installGuardedBinding(inst, slot, value, env.doc, slotName, () => h.call(env.facade));
976
+ }
977
+ }
978
+ function installAlias(inst, name, target, env) {
979
+ const [idName, ...rest] = target.path;
980
+ const targetInst = env.ctx.lookupId(idName);
981
+ if (!targetInst)
982
+ throw new Error(`QML: alias '${name}' targets unknown id '${idName}'`);
983
+ if (!rest.length)
984
+ throw new Error(`QML: alias '${name}' must target id.property`);
985
+ const slot = targetInst.slot(rest.join('.'));
986
+ inst.slots.set(name, slot); // the same Slot: reads and writes forward
987
+ }
988
+ function installValueSource(inst, m, env) {
989
+ const srcType = lookupType(env.doc, m.object.type);
990
+ const propName = m.on.join('.');
991
+ const slot = inst.slot(propName);
992
+ const srcInst = createTree(m.object, env.doc, env.ctx, inst);
993
+ activate(srcInst, []);
994
+ inst.valueSources.push(srcInst);
995
+ if (!srcType.init)
996
+ throw new Error(`QML: ${m.object.type} cannot be a value source here`);
997
+ srcType.init(srcInst, { targetSlot: slot, target: inst });
998
+ }
999
+ // --- anchors ---------------------------------------------------------------
1000
+ const px = (v) => typeof v === 'number' && Number.isFinite(v) ? v : 0;
1001
+ function lineCoord(line, forInst) {
1002
+ const { inst, edge } = line;
1003
+ const horizontal = edge === 'left' || edge === 'right' || edge === 'horizontalCenter';
1004
+ if (inst === forInst.parentInst) {
1005
+ const size = px(inst.slot(horizontal ? 'width' : 'height').get());
1006
+ if (edge === 'left' || edge === 'top')
1007
+ return 0;
1008
+ if (edge === 'right' || edge === 'bottom')
1009
+ return size;
1010
+ return size / 2;
1011
+ }
1012
+ // Sibling: the same coordinate space (both relative to the parent).
1013
+ const pos = px(inst.slot(horizontal ? 'x' : 'y').get());
1014
+ const size = px(inst.slot(horizontal ? 'width' : 'height').get());
1015
+ if (edge === 'left' || edge === 'top')
1016
+ return pos;
1017
+ if (edge === 'right' || edge === 'bottom')
1018
+ return pos + size;
1019
+ return pos + size / 2;
1020
+ }
1021
+ const isAnchorLine = (v) => !!v &&
1022
+ typeof v === 'object' &&
1023
+ 'inst' in v &&
1024
+ 'edge' in v &&
1025
+ v.inst instanceof QmlInstance;
1026
+ const ANCHOR_CONFIG_NAMES = [
1027
+ 'fill',
1028
+ 'centerIn',
1029
+ 'left',
1030
+ 'right',
1031
+ 'top',
1032
+ 'bottom',
1033
+ 'horizontalCenter',
1034
+ 'verticalCenter',
1035
+ ];
1036
+ function installAnchors(inst) {
1037
+ if (!inst.slots.has('anchors.fill'))
1038
+ return; // non-Item types
1039
+ const a = (n) => inst.slots.get(`anchors.${n}`)?.get();
1040
+ const margin = (side) => {
1041
+ const m = a(`${side}Margin`);
1042
+ return px(m !== undefined && m !== null ? m : a('margins'));
1043
+ };
1044
+ const line = (v) => (isAnchorLine(v) ? v : null);
1045
+ const xSlot = inst.slot('x');
1046
+ const ySlot = inst.slot('y');
1047
+ const wSlot = inst.slot('width');
1048
+ const hSlot = inst.slot('height');
1049
+ const install = () => {
1050
+ const fill = instanceOf(a('fill'));
1051
+ const centerIn = instanceOf(a('centerIn'));
1052
+ const L = fill ? fill.anchorLine('left') : line(a('left'));
1053
+ const R = fill ? fill.anchorLine('right') : line(a('right'));
1054
+ const T = fill ? fill.anchorLine('top') : line(a('top'));
1055
+ const B = fill ? fill.anchorLine('bottom') : line(a('bottom'));
1056
+ const HC = centerIn
1057
+ ? centerIn.anchorLine('horizontalCenter')
1058
+ : line(a('horizontalCenter'));
1059
+ const VC = centerIn
1060
+ ? centerIn.anchorLine('verticalCenter')
1061
+ : line(a('verticalCenter'));
1062
+ if (L && R) {
1063
+ wSlot.setBinding(() => lineCoord(R, inst) -
1064
+ margin('right') -
1065
+ (lineCoord(L, inst) + margin('left')));
1066
+ xSlot.setBinding(() => lineCoord(L, inst) + margin('left'));
1067
+ }
1068
+ else if (L) {
1069
+ xSlot.setBinding(() => lineCoord(L, inst) + margin('left'));
1070
+ }
1071
+ else if (R) {
1072
+ xSlot.setBinding(() => lineCoord(R, inst) - margin('right') - px(wSlot.get()));
1073
+ }
1074
+ else if (HC) {
1075
+ xSlot.setBinding(() => lineCoord(HC, inst) - px(wSlot.get()) / 2);
1076
+ }
1077
+ if (T && B) {
1078
+ hSlot.setBinding(() => lineCoord(B, inst) -
1079
+ margin('bottom') -
1080
+ (lineCoord(T, inst) + margin('top')));
1081
+ ySlot.setBinding(() => lineCoord(T, inst) + margin('top'));
1082
+ }
1083
+ else if (T) {
1084
+ ySlot.setBinding(() => lineCoord(T, inst) + margin('top'));
1085
+ }
1086
+ else if (B) {
1087
+ ySlot.setBinding(() => lineCoord(B, inst) - margin('bottom') - px(hSlot.get()));
1088
+ }
1089
+ else if (VC) {
1090
+ ySlot.setBinding(() => lineCoord(VC, inst) - px(hSlot.get()) / 2);
1091
+ }
1092
+ };
1093
+ const configured = ANCHOR_CONFIG_NAMES.some((n) => {
1094
+ const s = inst.slots.get(`anchors.${n}`);
1095
+ return s && (s.hasBinding() || s.peek() !== undefined);
1096
+ });
1097
+ // Re-anchoring at runtime re-installs the geometry bindings.
1098
+ for (const n of ANCHOR_CONFIG_NAMES)
1099
+ inst.slots.get(`anchors.${n}`)?.watch(install);
1100
+ if (configured)
1101
+ install();
1102
+ }
1103
+ //# sourceMappingURL=objects.js.map