@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,413 @@
1
+ // The QtQuick module: QML's core types over react-x11's intrinsic
2
+ // elements. The mapping table lives in docs/components/qml.md. The short
3
+ // version: Item/Rectangle are `<box>` with absolute geometry driven by the
4
+ // slot graph, Text is `<text>` with implicit size measured through the
5
+ // same ntk font stack the renderer lays out with, and every kind of motion
6
+ // (`Behavior`, state `Transition`s, looping animations) lowers onto the
7
+ // renderer's own `transition`/`animation` style engines — the renderer
8
+ // animates; the property jumps (the documented divergence).
9
+
10
+ import { useEffect } from 'react';
11
+ import type { ReactElement } from 'react';
12
+
13
+ import {
14
+ Qt,
15
+ QmlInstance,
16
+ registerQmlModule,
17
+ type QmlTypeDef,
18
+ type ValueSourceHookup,
19
+ } from './objects.js';
20
+ import { renderQmlChildren } from './react.js';
21
+ import {
22
+ anchorProperties,
23
+ applyMotion,
24
+ captureNode,
25
+ geometryStyle,
26
+ hostNode,
27
+ initItem,
28
+ num,
29
+ qmlColor,
30
+ str,
31
+ type MotionState,
32
+ } from './view-utils.js';
33
+ import {
34
+ applyFontStyle,
35
+ interactionTypes,
36
+ keyFocusProps,
37
+ } from './interaction.js';
38
+ import { containerTypes } from './containers.js';
39
+ import { layoutTypes } from './layouts.js';
40
+ import { modelTypes } from './models.js';
41
+ import { stateTypes } from './states.js';
42
+ import { cancel, schedule, warn } from './globals.js';
43
+
44
+ // Qt namespace constants used from expressions (`Qt.LeftButton`,
45
+ // `mouse.modifiers & Qt.ShiftModifier`, `Qt.AlignHCenter`).
46
+ Object.assign(Qt, {
47
+ LeftButton: 1,
48
+ RightButton: 2,
49
+ MiddleButton: 4,
50
+ ShiftModifier: 0x02000000,
51
+ ControlModifier: 0x04000000,
52
+ AltModifier: 0x08000000,
53
+ MetaModifier: 0x10000000,
54
+ AlignLeft: 1,
55
+ AlignRight: 2,
56
+ AlignHCenter: 4,
57
+ AlignTop: 32,
58
+ AlignBottom: 64,
59
+ AlignVCenter: 128,
60
+ });
61
+
62
+ // --- views -----------------------------------------------------------------
63
+
64
+ function ItemView({ inst }: { inst: QmlInstance }): ReactElement {
65
+ const style = geometryStyle(inst);
66
+ applyMotion(inst, style);
67
+ return (
68
+ <box ref={captureNode(inst)} style={style} {...keyFocusProps(inst)}>
69
+ {renderQmlChildren(inst)}
70
+ </box>
71
+ );
72
+ }
73
+
74
+ function RectangleView({ inst }: { inst: QmlInstance }): ReactElement {
75
+ const style = geometryStyle(inst);
76
+ style.backgroundColor = qmlColor(inst.slot('color').peek());
77
+ const radius = num(inst.slot('radius').peek());
78
+ if (radius) style.borderRadius = radius;
79
+ const bw = num(inst.slot('border.width').peek());
80
+ if (bw) {
81
+ style.borderWidth = bw;
82
+ style.borderColor = qmlColor(inst.slot('border.color').peek());
83
+ }
84
+ applyMotion(inst, style);
85
+ return (
86
+ <box ref={captureNode(inst)} style={style} {...keyFocusProps(inst)}>
87
+ {renderQmlChildren(inst)}
88
+ </box>
89
+ );
90
+ }
91
+
92
+ function TextView({ inst }: { inst: QmlInstance }): ReactElement {
93
+ const style = geometryStyle(inst, { contentSized: true });
94
+ applyFontStyle(inst, style);
95
+ const wrap = num(inst.slot('wrapMode').peek());
96
+ style.textWrap = wrap ? 'wrap' : 'nowrap';
97
+ const elide = num(inst.slot('elide').peek());
98
+ if (elide === 1 || elide === 2) {
99
+ style.textOverflow = 'ellipsis';
100
+ style.textWrap = 'nowrap';
101
+ }
102
+ const halign = num(inst.slot('horizontalAlignment').peek());
103
+ if (halign === 1) style.textAlign = 'left';
104
+ else if (halign === 2) style.textAlign = 'right';
105
+ else if (halign === 4) style.textAlign = 'center';
106
+ applyMotion(inst, style);
107
+ const text = str(inst.slot('text').peek());
108
+
109
+ // Implicit size, measured by the engine that will draw it. `assign`
110
+ // (never `set`) so this is not mistaken for interactive state.
111
+ useEffect(() => {
112
+ const node = hostNode(inst) as {
113
+ app?: {
114
+ fonts?: {
115
+ layout(t: string, s: unknown): { width: number; height: number };
116
+ };
117
+ };
118
+ resolvedTextStyle?(): unknown;
119
+ } | null;
120
+ if (!node?.app?.fonts || !node.resolvedTextStyle) return;
121
+ try {
122
+ const layout = node.app.fonts.layout(text, node.resolvedTextStyle());
123
+ inst.slots.get('implicitWidth')?.assign(Math.ceil(layout.width));
124
+ inst.slots.get('implicitHeight')?.assign(Math.ceil(layout.height));
125
+ } catch {
126
+ // No fonts loaded (headless without the fonts option): leave 0.
127
+ }
128
+ });
129
+
130
+ return (
131
+ <text ref={captureNode(inst)} style={style} {...keyFocusProps(inst)}>
132
+ {text}
133
+ </text>
134
+ );
135
+ }
136
+
137
+ const warnedFillMode = { done: false };
138
+ function ImageView({ inst }: { inst: QmlInstance }): ReactElement {
139
+ const style = geometryStyle(inst);
140
+ applyMotion(inst, style);
141
+ const source = str(inst.slot('source').peek());
142
+ if (num(inst.slot('fillMode').peek()) !== 0 && !warnedFillMode.done) {
143
+ warnedFillMode.done = true;
144
+ warn(
145
+ 'QML: Image.fillMode other than Stretch needs a fit mode in core ' +
146
+ '<image>; the image is stretched.',
147
+ );
148
+ }
149
+ if (!source) return <box style={style}>{renderQmlChildren(inst)}</box>;
150
+ return (
151
+ <image src={source} style={style}>
152
+ {renderQmlChildren(inst)}
153
+ </image>
154
+ );
155
+ }
156
+
157
+ // --- animation sources -----------------------------------------------------
158
+
159
+ function behaviorHookup(beh: QmlInstance, hookup: ValueSourceHookup): void {
160
+ const { targetSlot, target } = hookup;
161
+ const motion = target.state as MotionState;
162
+ (motion.behaviors ??= {})[targetSlot.name] = beh;
163
+ target._changed();
164
+ }
165
+
166
+ function numberAnimationHookup(
167
+ anim: QmlInstance,
168
+ hookup: ValueSourceHookup,
169
+ ): void {
170
+ const { targetSlot, target } = hookup;
171
+ const prop = targetSlot.name;
172
+ const motion = target.state as MotionState;
173
+ const sync = (): void => {
174
+ const loops = num(anim.slot('loops').peek());
175
+ const running = anim.slot('running').peek();
176
+ const from = anim.slot('from').peek();
177
+ const to = anim.slot('to').peek();
178
+ const duration = num(anim.slot('duration').peek()) || 250;
179
+ if (
180
+ loops === -1 &&
181
+ from !== undefined &&
182
+ to !== undefined &&
183
+ running !== false
184
+ ) {
185
+ // `loops: Animation.Infinite` — the style `animation` engine's case.
186
+ (motion.loopAnims ??= {})[prop] = { from, to, duration };
187
+ target._changed();
188
+ return;
189
+ }
190
+ if (motion.loopAnims?.[prop]) {
191
+ delete motion.loopAnims[prop];
192
+ target._changed();
193
+ }
194
+ // A property value source auto-runs unless told `running: false` —
195
+ // Qt's rule for `NumberAnimation on x`.
196
+ if (running === true || (running === undefined && to !== undefined)) {
197
+ // One crossing: commit `to`; the renderer eases from where it is
198
+ // (`from` is visual-only here — the documented divergence).
199
+ (motion.behaviors ??= {})[prop] = anim;
200
+ target._changed();
201
+ if (to !== undefined) targetSlot.assign(to);
202
+ cancel(anim.state.stopId);
203
+ anim.state.stopId = schedule(() => {
204
+ anim.slot('running').assign(false);
205
+ }, duration);
206
+ } else {
207
+ cancel(anim.state.stopId);
208
+ if (motion.behaviors?.[prop] === anim) {
209
+ delete motion.behaviors[prop];
210
+ target._changed();
211
+ }
212
+ }
213
+ };
214
+ anim.slot('running').watch(sync);
215
+ sync();
216
+ }
217
+
218
+ // --- Timer -----------------------------------------------------------------
219
+
220
+ function timerSync(inst: QmlInstance): void {
221
+ cancel(inst.state.timeout);
222
+ inst.state.timeout = null;
223
+ if (inst.destroyed || inst.slot('running').peek() !== true) return;
224
+ const tick = (): void => {
225
+ inst.state.timeout = schedule(
226
+ () => {
227
+ if (inst.destroyed) return;
228
+ if (inst.slot('repeat').peek() && inst.slot('running').peek()) tick();
229
+ else inst.slot('running').assign(false);
230
+ inst.emit('triggered');
231
+ },
232
+ num(inst.slot('interval').peek()),
233
+ );
234
+ };
235
+ tick();
236
+ }
237
+
238
+ // --- registration ----------------------------------------------------------
239
+
240
+ const TEXT_ENUMS = {
241
+ // Qt's actual values, so ported documents keep meaning.
242
+ NoWrap: 0,
243
+ WordWrap: 1,
244
+ WrapAnywhere: 3,
245
+ Wrap: 4,
246
+ ElideLeft: 0,
247
+ ElideRight: 1,
248
+ ElideMiddle: 2,
249
+ ElideNone: 3,
250
+ AlignLeft: 1,
251
+ AlignRight: 2,
252
+ AlignHCenter: 4,
253
+ AlignJustify: 8,
254
+ AlignTop: 32,
255
+ AlignBottom: 64,
256
+ AlignVCenter: 128,
257
+ };
258
+
259
+ const coreTypes: Record<string, QmlTypeDef> = {
260
+ QtObject: { nonVisual: true, properties: {} },
261
+
262
+ Item: {
263
+ view: ItemView,
264
+ init: initItem,
265
+ properties: {
266
+ x: { default: 0 },
267
+ y: { default: 0 },
268
+ width: { default: 0 },
269
+ height: { default: 0 },
270
+ implicitWidth: { default: 0 },
271
+ implicitHeight: { default: 0 },
272
+ visible: { default: true },
273
+ enabled: { default: true },
274
+ clip: { default: false },
275
+ z: { default: 0 },
276
+ opacity: { default: 1 },
277
+ rotation: { default: 0 },
278
+ scale: { default: 1 },
279
+ focus: { default: false },
280
+ activeFocus: { default: false },
281
+ state: { default: '' },
282
+ // Object arrays land as templates; these slots catch the empty and
283
+ // dynamic forms (`states: []`) without a diagnostic.
284
+ states: { default: undefined },
285
+ transitions: { default: undefined },
286
+ ...anchorProperties,
287
+ },
288
+ },
289
+
290
+ Rectangle: {
291
+ extends: 'Item',
292
+ view: RectangleView,
293
+ properties: {
294
+ color: { default: 'white' },
295
+ radius: { default: 0 },
296
+ 'border.width': { default: 0 },
297
+ 'border.color': { default: 'black' },
298
+ },
299
+ },
300
+
301
+ Text: {
302
+ extends: 'Item',
303
+ view: TextView,
304
+ properties: {
305
+ text: { default: '' },
306
+ color: { default: 'black' },
307
+ wrapMode: { default: 0 },
308
+ elide: { default: 3 }, // Qt.ElideNone
309
+ horizontalAlignment: { default: 0 },
310
+ verticalAlignment: { default: 0 },
311
+ 'font.pixelSize': { default: 0 },
312
+ 'font.pointSize': { default: 0 },
313
+ 'font.family': { default: '' },
314
+ 'font.bold': { default: false },
315
+ 'font.italic': { default: false },
316
+ 'font.weight': { default: 0 },
317
+ },
318
+ enums: TEXT_ENUMS,
319
+ },
320
+
321
+ Image: {
322
+ extends: 'Item',
323
+ view: ImageView,
324
+ properties: { source: { default: '' }, fillMode: { default: 0 } },
325
+ enums: {
326
+ Stretch: 0,
327
+ PreserveAspectFit: 1,
328
+ PreserveAspectCrop: 2,
329
+ Tile: 3,
330
+ },
331
+ },
332
+
333
+ Timer: {
334
+ nonVisual: true,
335
+ properties: {
336
+ interval: { default: 1000 },
337
+ running: { default: false },
338
+ repeat: { default: false },
339
+ },
340
+ signals: { triggered: [] },
341
+ init(inst) {
342
+ inst.methods.set('start', () => inst.slot('running').assign(true));
343
+ inst.methods.set('stop', () => inst.slot('running').assign(false));
344
+ inst.methods.set('restart', () => {
345
+ inst.slot('running').assign(false);
346
+ inst.slot('running').assign(true);
347
+ });
348
+ inst.slot('running').watch(() => timerSync(inst));
349
+ inst.slot('interval').watch(() => timerSync(inst));
350
+ timerSync(inst);
351
+ },
352
+ dispose(inst) {
353
+ cancel(inst.state.timeout);
354
+ },
355
+ },
356
+
357
+ Behavior: {
358
+ nonVisual: true,
359
+ properties: { enabled: { default: true } },
360
+ init(inst, hookup) {
361
+ if (hookup) behaviorHookup(inst, hookup);
362
+ },
363
+ },
364
+
365
+ /** The `Animation.Infinite` namespace. */
366
+ Animation: { nonVisual: true, enums: { Infinite: -1 } },
367
+
368
+ NumberAnimation: {
369
+ nonVisual: true,
370
+ properties: {
371
+ duration: { default: 250 },
372
+ from: { default: undefined },
373
+ to: { default: undefined },
374
+ loops: { default: 1 },
375
+ // Undefined so a value source can tell "never said" (auto-runs, as
376
+ // in Qt) from an explicit `running: false`.
377
+ running: { default: undefined },
378
+ properties: { default: '' }, // Transition's per-property list
379
+ property: { default: '' },
380
+ },
381
+ init(inst, hookup) {
382
+ if (hookup) numberAnimationHookup(inst, hookup);
383
+ },
384
+ dispose(inst) {
385
+ cancel(inst.state.stopId);
386
+ },
387
+ },
388
+ ColorAnimation: { extends: 'NumberAnimation', nonVisual: true },
389
+ PropertyAnimation: { extends: 'NumberAnimation', nonVisual: true },
390
+ };
391
+
392
+ let registered = false;
393
+
394
+ /** Populate the QtQuick registry — the family's one import-time side
395
+ * effect, called from `src/qml/index.ts` at module scope the way a
396
+ * component's `registerElement` is. */
397
+ export function registerQtQuick(): void {
398
+ if (registered) return;
399
+ registered = true;
400
+ registerQmlModule('QtQuick', {
401
+ version: '2.0',
402
+ types: {
403
+ ...coreTypes,
404
+ ...stateTypes,
405
+ ...modelTypes,
406
+ ...containerTypes,
407
+ ...interactionTypes,
408
+ },
409
+ });
410
+ registerQmlModule('QtQuick.Layouts', { version: '1.15', types: layoutTypes });
411
+ }
412
+
413
+ export { qmlColor };