@react-x11/components 0.3.0 → 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.
- package/README.md +6 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/qml/containers.d.ts +3 -0
- package/dist/qml/containers.d.ts.map +1 -0
- package/dist/qml/containers.js +331 -0
- package/dist/qml/containers.js.map +1 -0
- package/dist/qml/controls.d.ts +12 -0
- package/dist/qml/controls.d.ts.map +1 -0
- package/dist/qml/controls.js +21 -0
- package/dist/qml/controls.js.map +1 -0
- package/dist/qml/globals.d.ts +6 -0
- package/dist/qml/globals.d.ts.map +1 -0
- package/dist/qml/globals.js +25 -0
- package/dist/qml/globals.js.map +1 -0
- package/dist/qml/index.d.ts +15 -0
- package/dist/qml/index.d.ts.map +1 -0
- package/dist/qml/index.js +25 -0
- package/dist/qml/index.js.map +1 -0
- package/dist/qml/interaction.d.ts +12 -0
- package/dist/qml/interaction.d.ts.map +1 -0
- package/dist/qml/interaction.js +246 -0
- package/dist/qml/interaction.js.map +1 -0
- package/dist/qml/ir.d.ts +106 -0
- package/dist/qml/ir.d.ts.map +1 -0
- package/dist/qml/ir.js +7 -0
- package/dist/qml/ir.js.map +1 -0
- package/dist/qml/layouts.d.ts +3 -0
- package/dist/qml/layouts.d.ts.map +1 -0
- package/dist/qml/layouts.js +122 -0
- package/dist/qml/layouts.js.map +1 -0
- package/dist/qml/models.d.ts +14 -0
- package/dist/qml/models.d.ts.map +1 -0
- package/dist/qml/models.js +80 -0
- package/dist/qml/models.js.map +1 -0
- package/dist/qml/objects.d.ts +228 -0
- package/dist/qml/objects.d.ts.map +1 -0
- package/dist/qml/objects.js +1103 -0
- package/dist/qml/objects.js.map +1 -0
- package/dist/qml/parse.d.ts +10 -0
- package/dist/qml/parse.d.ts.map +1 -0
- package/dist/qml/parse.js +607 -0
- package/dist/qml/parse.js.map +1 -0
- package/dist/qml/qtquick.d.ts +7 -0
- package/dist/qml/qtquick.d.ts.map +1 -0
- package/dist/qml/qtquick.js +347 -0
- package/dist/qml/qtquick.js.map +1 -0
- package/dist/qml/react.d.ts +83 -0
- package/dist/qml/react.d.ts.map +1 -0
- package/dist/qml/react.js +323 -0
- package/dist/qml/react.js.map +1 -0
- package/dist/qml/resolver.d.ts +38 -0
- package/dist/qml/resolver.d.ts.map +1 -0
- package/dist/qml/resolver.js +64 -0
- package/dist/qml/resolver.js.map +1 -0
- package/dist/qml/slots.d.ts +65 -0
- package/dist/qml/slots.d.ts.map +1 -0
- package/dist/qml/slots.js +230 -0
- package/dist/qml/slots.js.map +1 -0
- package/dist/qml/states.d.ts +5 -0
- package/dist/qml/states.d.ts.map +1 -0
- package/dist/qml/states.js +276 -0
- package/dist/qml/states.js.map +1 -0
- package/dist/qml/view-utils.d.ts +42 -0
- package/dist/qml/view-utils.d.ts.map +1 -0
- package/dist/qml/view-utils.js +121 -0
- package/dist/qml/view-utils.js.map +1 -0
- package/dist/tabs/hx.d.ts +18 -0
- package/dist/tabs/hx.d.ts.map +1 -0
- package/dist/tabs/hx.js +10 -0
- package/dist/tabs/hx.js.map +1 -0
- package/dist/tabs/index.d.ts +143 -0
- package/dist/tabs/index.d.ts.map +1 -0
- package/dist/tabs/index.js +602 -0
- package/dist/tabs/index.js.map +1 -0
- package/dist/terminal/vt/index.d.ts +1 -1
- package/dist/terminal/vt/index.d.ts.map +1 -1
- package/dist/terminal/vt/index.js +3 -3
- package/dist/terminal/vt/index.js.map +1 -1
- package/dist/terminal/vt/pty.d.ts +16 -0
- package/dist/terminal/vt/pty.d.ts.map +1 -1
- package/dist/terminal/vt/pty.js +186 -0
- package/dist/terminal/vt/pty.js.map +1 -1
- package/package.json +11 -1
- package/src/index.ts +44 -0
- package/src/qml/containers.tsx +380 -0
- package/src/qml/controls.tsx +47 -0
- package/src/qml/globals.ts +33 -0
- package/src/qml/index.ts +67 -0
- package/src/qml/interaction.tsx +317 -0
- package/src/qml/ir.ts +80 -0
- package/src/qml/layouts.tsx +141 -0
- package/src/qml/models.ts +101 -0
- package/src/qml/objects.ts +1431 -0
- package/src/qml/parse.ts +624 -0
- package/src/qml/qtquick.tsx +413 -0
- package/src/qml/react.tsx +454 -0
- package/src/qml/resolver.ts +99 -0
- package/src/qml/slots.ts +249 -0
- package/src/qml/states.ts +318 -0
- package/src/qml/view-utils.ts +140 -0
- package/src/tabs/hx.ts +31 -0
- package/src/tabs/index.ts +850 -0
- package/src/terminal/vt/index.ts +4 -2
- package/src/terminal/vt/pty.ts +243 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
// Containers: the positioners (bindings the Row installs on its children),
|
|
2
|
+
// Repeater, Loader, and the scroll pair — Flickable over core's
|
|
3
|
+
// `overflow: 'scroll'` box, ListView over Flickable with windowed
|
|
4
|
+
// delegates: only the visible range (plus a buffer) is instantiated, and
|
|
5
|
+
// scrolling moves the window.
|
|
6
|
+
|
|
7
|
+
import { useEffect } from 'react';
|
|
8
|
+
import type { ReactElement } from 'react';
|
|
9
|
+
import type { ScrollableNode } from 'react-x11';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
QmlInstance,
|
|
13
|
+
instantiateComponentDoc,
|
|
14
|
+
instantiateTemplate,
|
|
15
|
+
resolveComponentByPath,
|
|
16
|
+
type QmlTypeDef,
|
|
17
|
+
} from './objects.js';
|
|
18
|
+
import { renderQmlChildren, geometryStyle } from './react.js';
|
|
19
|
+
import {
|
|
20
|
+
applyMotion,
|
|
21
|
+
captureNode,
|
|
22
|
+
hostNode,
|
|
23
|
+
initItem,
|
|
24
|
+
num,
|
|
25
|
+
} from './view-utils.js';
|
|
26
|
+
import { delegateExtras, resolveModel } from './models.js';
|
|
27
|
+
import { warn } from './globals.js';
|
|
28
|
+
|
|
29
|
+
// --- positioners -----------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
function positionerLayout(inst: QmlInstance): void {
|
|
32
|
+
const horizontal = inst.typeInfo.name === 'Row';
|
|
33
|
+
const main = horizontal ? 'x' : 'y';
|
|
34
|
+
const mainSize = horizontal ? 'width' : 'height';
|
|
35
|
+
const crossSize = horizontal ? 'height' : 'width';
|
|
36
|
+
for (const kid of inst.visualChildren()) {
|
|
37
|
+
kid.slot(main).setBinding(() => {
|
|
38
|
+
let pos = 0;
|
|
39
|
+
const spacing = num(inst.slot('spacing').get());
|
|
40
|
+
for (const prev of inst.visualChildren()) {
|
|
41
|
+
if (prev === kid) break;
|
|
42
|
+
if (prev.slot('visible').get() === false) continue;
|
|
43
|
+
pos += num(prev.slot(mainSize).get()) + spacing;
|
|
44
|
+
}
|
|
45
|
+
return pos;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const extent = (): number => {
|
|
49
|
+
let sum = 0;
|
|
50
|
+
let count = 0;
|
|
51
|
+
const spacing = num(inst.slot('spacing').get());
|
|
52
|
+
for (const kid of inst.visualChildren()) {
|
|
53
|
+
if (kid.slot('visible').get() === false) continue;
|
|
54
|
+
sum += num(kid.slot(mainSize).get());
|
|
55
|
+
count++;
|
|
56
|
+
}
|
|
57
|
+
return sum + spacing * Math.max(0, count - 1);
|
|
58
|
+
};
|
|
59
|
+
const crossExtent = (): number => {
|
|
60
|
+
let max = 0;
|
|
61
|
+
for (const kid of inst.visualChildren()) {
|
|
62
|
+
if (kid.slot('visible').get() === false) continue;
|
|
63
|
+
max = Math.max(max, num(kid.slot(crossSize).get()));
|
|
64
|
+
}
|
|
65
|
+
return max;
|
|
66
|
+
};
|
|
67
|
+
inst.slot(horizontal ? 'implicitWidth' : 'implicitHeight').setBinding(extent);
|
|
68
|
+
inst
|
|
69
|
+
.slot(horizontal ? 'implicitHeight' : 'implicitWidth')
|
|
70
|
+
.setBinding(crossExtent);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function positionerInit(inst: QmlInstance): void {
|
|
74
|
+
initItem(inst);
|
|
75
|
+
positionerLayout(inst);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// --- Repeater --------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
interface RepeaterState {
|
|
81
|
+
items?: QmlInstance[];
|
|
82
|
+
revOff?: (() => void) | null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function repeaterRebuild(inst: QmlInstance): void {
|
|
86
|
+
const parent = inst.parentInst;
|
|
87
|
+
const tpl = inst.templates.get('delegate');
|
|
88
|
+
if (!parent || !tpl) return;
|
|
89
|
+
const state = inst.state as RepeaterState;
|
|
90
|
+
for (const item of state.items ?? []) {
|
|
91
|
+
const at = parent.children.indexOf(item);
|
|
92
|
+
if (at >= 0) parent.children.splice(at, 1);
|
|
93
|
+
item.destroy();
|
|
94
|
+
}
|
|
95
|
+
const { rows } = resolveModel(inst.slot('model').peek());
|
|
96
|
+
const items = rows.map(
|
|
97
|
+
(_, index) =>
|
|
98
|
+
instantiateTemplate(tpl, parent, delegateExtras(rows, index)).inst,
|
|
99
|
+
);
|
|
100
|
+
const at = parent.children.indexOf(inst);
|
|
101
|
+
parent.children.splice(at + 1, 0, ...items);
|
|
102
|
+
state.items = items;
|
|
103
|
+
inst.slot('count').assign(items.length);
|
|
104
|
+
parent._structChanged();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function repeaterModelChanged(inst: QmlInstance): void {
|
|
108
|
+
const state = inst.state as RepeaterState;
|
|
109
|
+
state.revOff?.();
|
|
110
|
+
state.revOff = null;
|
|
111
|
+
const { revSlot } = resolveModel(inst.slot('model').peek());
|
|
112
|
+
if (revSlot) state.revOff = revSlot.watch(() => repeaterRebuild(inst));
|
|
113
|
+
repeaterRebuild(inst);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// --- Flickable -------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
function FlickableView({ inst }: { inst: QmlInstance }): ReactElement {
|
|
119
|
+
const style = geometryStyle(inst);
|
|
120
|
+
style.overflow = 'scroll';
|
|
121
|
+
applyMotion(inst, style);
|
|
122
|
+
const cw = num(inst.slot('contentWidth').peek());
|
|
123
|
+
const ch = num(inst.slot('contentHeight').peek());
|
|
124
|
+
const cx = num(inst.slot('contentX').peek());
|
|
125
|
+
const cy = num(inst.slot('contentY').peek());
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
const node = hostNode(inst) as ScrollableNode | null;
|
|
128
|
+
if (!node || typeof node.scrollTo !== 'function') return;
|
|
129
|
+
if (node.scrollX !== cx || node.scrollY !== cy)
|
|
130
|
+
node.scrollTo({ x: cx, y: cy });
|
|
131
|
+
}, [inst, cx, cy]);
|
|
132
|
+
return (
|
|
133
|
+
<box
|
|
134
|
+
ref={captureNode(inst)}
|
|
135
|
+
style={style}
|
|
136
|
+
onScroll={(ev: { scrollX: number; scrollY: number }) => {
|
|
137
|
+
inst.slot('contentX').assign(ev.scrollX);
|
|
138
|
+
inst.slot('contentY').assign(ev.scrollY);
|
|
139
|
+
}}
|
|
140
|
+
>
|
|
141
|
+
<box
|
|
142
|
+
style={{
|
|
143
|
+
position: 'relative',
|
|
144
|
+
width: cw || undefined,
|
|
145
|
+
height: ch || undefined,
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
{renderQmlChildren(inst)}
|
|
149
|
+
</box>
|
|
150
|
+
</box>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// --- ListView --------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
interface ListState {
|
|
157
|
+
items?: Map<number, QmlInstance>;
|
|
158
|
+
revOff?: (() => void) | null;
|
|
159
|
+
measuredWatch?: boolean;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function listViewInit(lv: QmlInstance): void {
|
|
163
|
+
initItem(lv);
|
|
164
|
+
const state = lv.state as ListState;
|
|
165
|
+
const items = new Map<number, QmlInstance>();
|
|
166
|
+
state.items = items;
|
|
167
|
+
|
|
168
|
+
const destroyItem = (i: number): void => {
|
|
169
|
+
const it = items.get(i);
|
|
170
|
+
if (!it) return;
|
|
171
|
+
const at = lv.children.indexOf(it);
|
|
172
|
+
if (at >= 0) lv.children.splice(at, 1);
|
|
173
|
+
it.destroy();
|
|
174
|
+
items.delete(i);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const update = (): void => {
|
|
178
|
+
if (lv.destroyed) return;
|
|
179
|
+
const tpl = lv.templates.get('delegate');
|
|
180
|
+
if (!tpl) return;
|
|
181
|
+
const { rows } = resolveModel(lv.slot('model').peek());
|
|
182
|
+
const spacing = num(lv.slot('spacing').peek());
|
|
183
|
+
const count = rows.length;
|
|
184
|
+
for (const i of [...items.keys()]) if (i >= count) destroyItem(i);
|
|
185
|
+
if (count === 0) {
|
|
186
|
+
lv.slot('contentHeight').assign(0);
|
|
187
|
+
lv._structChanged();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const ensure = (i: number): QmlInstance => {
|
|
191
|
+
let it = items.get(i);
|
|
192
|
+
if (!it) {
|
|
193
|
+
it = instantiateTemplate(tpl, lv, delegateExtras(rows, i)).inst;
|
|
194
|
+
items.set(i, it);
|
|
195
|
+
lv.children.push(it);
|
|
196
|
+
}
|
|
197
|
+
return it;
|
|
198
|
+
};
|
|
199
|
+
const first = ensure(0);
|
|
200
|
+
if (!state.measuredWatch) {
|
|
201
|
+
state.measuredWatch = true;
|
|
202
|
+
first.slot('height').watch(update); // implicit sizes settle late
|
|
203
|
+
}
|
|
204
|
+
const h = num(first.slot('height').peek());
|
|
205
|
+
if (!h) {
|
|
206
|
+
// Nothing measurable yet: stack whatever heights the delegates say.
|
|
207
|
+
let y = 0;
|
|
208
|
+
for (let i = 0; i < count; i++) {
|
|
209
|
+
const it = ensure(i);
|
|
210
|
+
it.slot('y').assign(y);
|
|
211
|
+
y += num(it.slot('height').peek()) + spacing;
|
|
212
|
+
}
|
|
213
|
+
lv.slot('contentHeight').assign(Math.max(0, y - spacing));
|
|
214
|
+
} else {
|
|
215
|
+
// Uniform rows (the first row's height): window the range.
|
|
216
|
+
const stride = h + spacing;
|
|
217
|
+
const viewH = num(lv.slot('height').peek());
|
|
218
|
+
const cy = num(lv.slot('contentY').peek());
|
|
219
|
+
const buffer = Math.max(2, Math.ceil((viewH || stride) / stride));
|
|
220
|
+
const start = Math.max(0, Math.floor(cy / stride) - buffer);
|
|
221
|
+
const end = Math.min(
|
|
222
|
+
count - 1,
|
|
223
|
+
Math.ceil((cy + (viewH || stride)) / stride) + buffer,
|
|
224
|
+
);
|
|
225
|
+
for (const i of [...items.keys()]) {
|
|
226
|
+
if (i !== 0 && (i < start || i > end)) destroyItem(i);
|
|
227
|
+
}
|
|
228
|
+
for (let i = start; i <= end; i++)
|
|
229
|
+
ensure(i)
|
|
230
|
+
.slot('y')
|
|
231
|
+
.assign(i * stride);
|
|
232
|
+
first.slot('y').assign(0);
|
|
233
|
+
lv.slot('contentHeight').assign(count * stride - spacing);
|
|
234
|
+
}
|
|
235
|
+
lv._structChanged();
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const fullRebuild = (): void => {
|
|
239
|
+
for (const i of [...items.keys()]) destroyItem(i);
|
|
240
|
+
state.measuredWatch = false;
|
|
241
|
+
update();
|
|
242
|
+
};
|
|
243
|
+
const modelChanged = (): void => {
|
|
244
|
+
state.revOff?.();
|
|
245
|
+
state.revOff = null;
|
|
246
|
+
const { revSlot } = resolveModel(lv.slot('model').peek());
|
|
247
|
+
if (revSlot) state.revOff = revSlot.watch(fullRebuild);
|
|
248
|
+
fullRebuild();
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
lv.slot('model').watch(modelChanged);
|
|
252
|
+
lv.slot('contentY').watch(update);
|
|
253
|
+
lv.slot('height').watch(update);
|
|
254
|
+
lv.slot('spacing').watch(fullRebuild);
|
|
255
|
+
modelChanged();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// --- Loader ----------------------------------------------------------------
|
|
259
|
+
|
|
260
|
+
function loaderInit(ld: QmlInstance): void {
|
|
261
|
+
initItem(ld);
|
|
262
|
+
let child: QmlInstance | null = null;
|
|
263
|
+
const sync = (): void => {
|
|
264
|
+
if (child) {
|
|
265
|
+
const at = ld.children.indexOf(child);
|
|
266
|
+
if (at >= 0) ld.children.splice(at, 1);
|
|
267
|
+
child.destroy();
|
|
268
|
+
child = null;
|
|
269
|
+
ld.slot('item').assign(null);
|
|
270
|
+
ld.slot('implicitWidth').assign(0);
|
|
271
|
+
ld.slot('implicitHeight').assign(0);
|
|
272
|
+
}
|
|
273
|
+
const tpl = ld.templates.get('sourceComponent');
|
|
274
|
+
const sourcePath = String(ld.slot('source').peek() ?? '');
|
|
275
|
+
const active = ld.slot('active').peek() !== false;
|
|
276
|
+
let loaded: QmlInstance | null = null;
|
|
277
|
+
if (!ld.destroyed && active && tpl) {
|
|
278
|
+
loaded = instantiateTemplate(tpl, ld).inst;
|
|
279
|
+
} else if (!ld.destroyed && active && sourcePath) {
|
|
280
|
+
// `source: "widgets/Meter.qml"` — a component document through the
|
|
281
|
+
// resolver seam, relative to the document that names it.
|
|
282
|
+
const compDoc = resolveComponentByPath(ld.doc, sourcePath);
|
|
283
|
+
if (compDoc) {
|
|
284
|
+
loaded = instantiateComponentDoc(compDoc, ld.context, ld).inst;
|
|
285
|
+
} else {
|
|
286
|
+
warn(
|
|
287
|
+
`QML: Loader.source '${sourcePath}' did not resolve — ` +
|
|
288
|
+
(ld.doc.load
|
|
289
|
+
? 'no such .qml file relative to this document.'
|
|
290
|
+
: 'pass a resolver (createFileResolver) to QmlView.'),
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (loaded) {
|
|
295
|
+
child = loaded;
|
|
296
|
+
const item = loaded;
|
|
297
|
+
ld.children.push(item);
|
|
298
|
+
ld.slot('item').assign(item.facade);
|
|
299
|
+
ld.slot('implicitWidth').setBinding(() => num(item.slot('width').get()));
|
|
300
|
+
ld.slot('implicitHeight').setBinding(() =>
|
|
301
|
+
num(item.slot('height').get()),
|
|
302
|
+
);
|
|
303
|
+
ld.emit('loaded');
|
|
304
|
+
}
|
|
305
|
+
ld._structChanged();
|
|
306
|
+
};
|
|
307
|
+
ld.slot('active').watch(sync);
|
|
308
|
+
ld.slot('source').watch(sync);
|
|
309
|
+
sync();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// --- the type definitions --------------------------------------------------
|
|
313
|
+
|
|
314
|
+
export const containerTypes: Record<string, QmlTypeDef> = {
|
|
315
|
+
Row: {
|
|
316
|
+
extends: 'Item',
|
|
317
|
+
properties: { spacing: { default: 0 } },
|
|
318
|
+
init: positionerInit,
|
|
319
|
+
onStructure: positionerLayout,
|
|
320
|
+
},
|
|
321
|
+
Column: {
|
|
322
|
+
extends: 'Item',
|
|
323
|
+
properties: { spacing: { default: 0 } },
|
|
324
|
+
init: positionerInit,
|
|
325
|
+
onStructure: positionerLayout,
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
Repeater: {
|
|
329
|
+
nonVisual: true,
|
|
330
|
+
capture: 'delegate',
|
|
331
|
+
properties: { model: { default: 0 }, count: { default: 0 } },
|
|
332
|
+
init(inst) {
|
|
333
|
+
inst.slot('model').watch(() => repeaterModelChanged(inst));
|
|
334
|
+
repeaterModelChanged(inst);
|
|
335
|
+
},
|
|
336
|
+
dispose(inst) {
|
|
337
|
+
const state = inst.state as RepeaterState;
|
|
338
|
+
state.revOff?.();
|
|
339
|
+
for (const item of state.items ?? []) item.destroy();
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
Flickable: {
|
|
344
|
+
extends: 'Item',
|
|
345
|
+
view: FlickableView,
|
|
346
|
+
properties: {
|
|
347
|
+
contentWidth: { default: 0 },
|
|
348
|
+
contentHeight: { default: 0 },
|
|
349
|
+
contentX: { default: 0 },
|
|
350
|
+
contentY: { default: 0 },
|
|
351
|
+
},
|
|
352
|
+
init: initItem,
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
ListView: {
|
|
356
|
+
extends: 'Flickable',
|
|
357
|
+
capture: 'delegate',
|
|
358
|
+
properties: {
|
|
359
|
+
model: { default: 0 },
|
|
360
|
+
spacing: { default: 0 },
|
|
361
|
+
},
|
|
362
|
+
init: listViewInit,
|
|
363
|
+
dispose(lv) {
|
|
364
|
+
const state = lv.state as ListState;
|
|
365
|
+
state.revOff?.();
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
Loader: {
|
|
370
|
+
extends: 'Item',
|
|
371
|
+
capture: 'sourceComponent',
|
|
372
|
+
properties: {
|
|
373
|
+
active: { default: true },
|
|
374
|
+
item: { default: null },
|
|
375
|
+
source: { default: '' },
|
|
376
|
+
},
|
|
377
|
+
signals: { loaded: [] },
|
|
378
|
+
init: loaderInit,
|
|
379
|
+
},
|
|
380
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// `import QtQuick.Controls` — the beginnings of the Controls story:
|
|
2
|
+
// widgets react-x11 already ships, registered as QML types. The widgets
|
|
3
|
+
// are passed in rather than imported here, so the family costs nothing
|
|
4
|
+
// when Controls are unused and the app decides which widget set answers
|
|
5
|
+
// (core's `Button`, or its own).
|
|
6
|
+
|
|
7
|
+
import type { ComponentType } from 'react';
|
|
8
|
+
import { registerQmlModule } from './objects.js';
|
|
9
|
+
import { geometryStyle } from './react.js';
|
|
10
|
+
import { str } from './view-utils.js';
|
|
11
|
+
|
|
12
|
+
export interface ControlsWidgets {
|
|
13
|
+
/** A pressable taking `primary`, `disabled`, `onPress` and children —
|
|
14
|
+
* core's `Button` as it stands. Deliberately `ComponentType<any>`:
|
|
15
|
+
* React's `propTypes`/`defaultProps` members make component types
|
|
16
|
+
* near-invariant in their props, so a props-typed seam rejects any
|
|
17
|
+
* richer button; `any` is the honest edge (eslint does not see
|
|
18
|
+
* TypeScript here — AGENTS.md, "Linting"). */
|
|
19
|
+
Button: ComponentType<any>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function registerControls({ Button }: ControlsWidgets): void {
|
|
23
|
+
registerQmlModule('QtQuick.Controls', {
|
|
24
|
+
version: '2.0',
|
|
25
|
+
types: {
|
|
26
|
+
Button: {
|
|
27
|
+
extends: 'Item',
|
|
28
|
+
properties: {
|
|
29
|
+
text: { default: '' },
|
|
30
|
+
primary: { default: false },
|
|
31
|
+
},
|
|
32
|
+
signals: { clicked: [] },
|
|
33
|
+
view: ({ inst }) => (
|
|
34
|
+
<box style={geometryStyle(inst)}>
|
|
35
|
+
<Button
|
|
36
|
+
primary={inst.slot('primary').peek() === true}
|
|
37
|
+
disabled={inst.slot('enabled').peek() === false}
|
|
38
|
+
onPress={() => inst.emit('clicked')}
|
|
39
|
+
>
|
|
40
|
+
{str(inst.slot('text').peek())}
|
|
41
|
+
</Button>
|
|
42
|
+
</box>
|
|
43
|
+
),
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Host globals, reached structurally through `globalThis`. The build
|
|
2
|
+
// compiles `src/` with `types: []` on purpose — a Node global that wanders
|
|
3
|
+
// in must fail the build rather than become an implicit `@types/node`
|
|
4
|
+
// dependency (AGENTS.md) — so the few platform facilities the QML runtime
|
|
5
|
+
// needs are typed here, the way `charts/timers.ts` and `three/globals.ts`
|
|
6
|
+
// do it.
|
|
7
|
+
|
|
8
|
+
interface HostGlobals {
|
|
9
|
+
queueMicrotask?(fn: () => void): void;
|
|
10
|
+
setTimeout?(fn: () => void, ms: number): unknown;
|
|
11
|
+
clearTimeout?(id: unknown): void;
|
|
12
|
+
console?: { warn(...args: unknown[]): void };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const g = globalThis as HostGlobals;
|
|
16
|
+
|
|
17
|
+
export function microtask(fn: () => void): void {
|
|
18
|
+
if (g.queueMicrotask) g.queueMicrotask(fn);
|
|
19
|
+
else void Promise.resolve().then(fn);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** `Timer { interval: … }` — null when the host has no timer at all. */
|
|
23
|
+
export function schedule(fn: () => void, ms: number): unknown {
|
|
24
|
+
return g.setTimeout ? g.setTimeout(fn, ms) : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function cancel(id: unknown): void {
|
|
28
|
+
if (id != null) g.clearTimeout?.(id);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function warn(...args: unknown[]): void {
|
|
32
|
+
g.console?.warn(...args);
|
|
33
|
+
}
|
package/src/qml/index.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// `@react-x11/components/qml` — QML as an authoring layer over react-x11.
|
|
2
|
+
//
|
|
3
|
+
// A zero-dependency QML engine: the parser, the reactive slot graph with
|
|
4
|
+
// QML's binding-breaking assignment, the object model with Qt's scoping
|
|
5
|
+
// rules, and a QtQuick subset whose every pixel is an ordinary
|
|
6
|
+
// `<box>`/`<text>`/`<image>` committed through the renderer. The docs page
|
|
7
|
+
// is docs/components/qml.md; the design record is react-x11's QML RFC.
|
|
8
|
+
//
|
|
9
|
+
// Importing this module populates the QtQuick registry — the family's own
|
|
10
|
+
// module-level registry, no core state touched. That is this family's one
|
|
11
|
+
// import-time side effect, the same shape as a component registering its
|
|
12
|
+
// element (AGENTS.md, "Tree-shaking"): it lives here so a bundler that
|
|
13
|
+
// keeps the family runs it, and one that shakes the family drops it.
|
|
14
|
+
|
|
15
|
+
import { registerQtQuick } from './qtquick.js';
|
|
16
|
+
|
|
17
|
+
registerQtQuick();
|
|
18
|
+
|
|
19
|
+
export { parseQml, ParseError } from './parse.js';
|
|
20
|
+
export type {
|
|
21
|
+
QmlDocument,
|
|
22
|
+
QmlImport,
|
|
23
|
+
ObjectIR,
|
|
24
|
+
MemberIR,
|
|
25
|
+
ValueIR,
|
|
26
|
+
} from './ir.js';
|
|
27
|
+
export {
|
|
28
|
+
Qt,
|
|
29
|
+
QmlInstance,
|
|
30
|
+
Context,
|
|
31
|
+
instantiateDocument,
|
|
32
|
+
instantiateTemplate,
|
|
33
|
+
migrateUserState,
|
|
34
|
+
registerQmlModule,
|
|
35
|
+
instanceOf,
|
|
36
|
+
lookupType,
|
|
37
|
+
} from './objects.js';
|
|
38
|
+
export type {
|
|
39
|
+
QmlFacade,
|
|
40
|
+
QmlTypeDef,
|
|
41
|
+
QmlTypeInfo,
|
|
42
|
+
QmlPropertyDef,
|
|
43
|
+
ValueSourceHookup,
|
|
44
|
+
InstantiateResult,
|
|
45
|
+
QtNamespace,
|
|
46
|
+
TemplateRef,
|
|
47
|
+
} from './objects.js';
|
|
48
|
+
export { Slot, flushBindings } from './slots.js';
|
|
49
|
+
export {
|
|
50
|
+
QmlView,
|
|
51
|
+
QmlNode,
|
|
52
|
+
registerReactComponent,
|
|
53
|
+
renderQmlChildren,
|
|
54
|
+
useQmlVersion,
|
|
55
|
+
} from './react.js';
|
|
56
|
+
export type {
|
|
57
|
+
QmlViewProps,
|
|
58
|
+
QmlViewHandle,
|
|
59
|
+
RegisterReactComponentOptions,
|
|
60
|
+
} from './react.js';
|
|
61
|
+
export { qmlColor, registerQtQuick } from './qtquick.js';
|
|
62
|
+
export { createFileResolver } from './resolver.js';
|
|
63
|
+
export type { QmlResolver, DocLoad } from './resolver.js';
|
|
64
|
+
export { geometryStyle, preferredSpan } from './react.js';
|
|
65
|
+
export { captureNode, hostNode } from './view-utils.js';
|
|
66
|
+
export { registerControls } from './controls.js';
|
|
67
|
+
export type { ControlsWidgets } from './controls.js';
|