quickwin 1.0.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 (175) hide show
  1. package/README.en.md +197 -0
  2. package/README.md +196 -0
  3. package/examples/PdfCanvas.js +67 -0
  4. package/examples/PdfCanvas.tsx +83 -0
  5. package/examples/pdf_preview2.js +431 -0
  6. package/examples/pdf_preview2.ts +375 -0
  7. package/examples/pdf_viewer.js +17623 -0
  8. package/examples/pdf_viewer.tsx +279 -0
  9. package/examples/test_gallery.js +17452 -0
  10. package/examples/test_gallery.tsx +285 -0
  11. package/examples/test_react_lazy.js +17336 -0
  12. package/examples/test_react_lazy.tsx +63 -0
  13. package/examples/test_react_listbox.js +17339 -0
  14. package/examples/test_react_listbox.tsx +71 -0
  15. package/examples/test_root_window.js +17313 -0
  16. package/examples/test_root_window.tsx +41 -0
  17. package/examples/test_tab.js +17339 -0
  18. package/examples/test_tab.tsx +75 -0
  19. package/examples/tray_demo.js +79 -0
  20. package/examples/tray_demo.tsx +81 -0
  21. package/lib/assert.js +8 -0
  22. package/lib/assert.ts +7 -0
  23. package/lib/cache-utils.js +43 -0
  24. package/lib/cache-utils.ts +40 -0
  25. package/lib/dumpRects.js +25 -0
  26. package/lib/dumpRects.ts +25 -0
  27. package/lib/fetch.js +490 -0
  28. package/lib/fetch.ts +502 -0
  29. package/lib/http-common.js +342 -0
  30. package/lib/http-common.ts +399 -0
  31. package/lib/http-server.js +416 -0
  32. package/lib/http-server.ts +424 -0
  33. package/lib/polyfill.js +156 -0
  34. package/lib/polyfill.ts +171 -0
  35. package/lib/react-qw/components/Button.js +8 -0
  36. package/lib/react-qw/components/CheckBox.js +31 -0
  37. package/lib/react-qw/components/ComboBox.js +42 -0
  38. package/lib/react-qw/components/DateTimePicker.js +96 -0
  39. package/lib/react-qw/components/Input.js +50 -0
  40. package/lib/react-qw/components/Link.js +43 -0
  41. package/lib/react-qw/components/ListBox.js +48 -0
  42. package/lib/react-qw/components/ListView.js +328 -0
  43. package/lib/react-qw/components/PathPicker.js +136 -0
  44. package/lib/react-qw/components/ProgressBar.js +22 -0
  45. package/lib/react-qw/components/RadioButton.js +31 -0
  46. package/lib/react-qw/components/ScrollView.js +139 -0
  47. package/lib/react-qw/components/Slider.js +45 -0
  48. package/lib/react-qw/components/Tab.js +61 -0
  49. package/lib/react-qw/components/Tooltip.js +55 -0
  50. package/lib/react-qw/components/TreeView.js +130 -0
  51. package/lib/react-qw/components/index.js +16 -0
  52. package/lib/react-qw/components/index.ts +17 -0
  53. package/lib/react-qw/index.js +73 -0
  54. package/lib/react-qw/index.ts +90 -0
  55. package/lib/react-qw/jsx.d.ts +34 -0
  56. package/lib/react-qw/layout.js +82 -0
  57. package/lib/react-qw/layout.ts +111 -0
  58. package/lib/react-qw/props.js +27 -0
  59. package/lib/react-qw/props.ts +36 -0
  60. package/lib/react-qw/reconciler.js +333 -0
  61. package/lib/react-qw/reconciler.ts +373 -0
  62. package/lib/stream.js +2 -0
  63. package/lib/stream.ts +22 -0
  64. package/lib/text-codec.js +197 -0
  65. package/lib/text-codec.ts +171 -0
  66. package/lib/text-measure.js +52 -0
  67. package/lib/text-measure.ts +54 -0
  68. package/lib/url.js +278 -0
  69. package/lib/url.ts +201 -0
  70. package/lib/vendor/web-streams/ponyfill.d.mts +829 -0
  71. package/lib/vendor/web-streams/ponyfill.mjs +8 -0
  72. package/lib/websocket.js +541 -0
  73. package/lib/websocket.ts +555 -0
  74. package/package.json +44 -0
  75. package/quickwin.d.ts +594 -0
  76. package/quickwin_const.d.ts +900 -0
  77. package/qwin-nowasm.exe +0 -0
  78. package/qwin.exe +0 -0
  79. package/test/add.wasm +0 -0
  80. package/test/complex.wasm +0 -0
  81. package/test/complex_imports.wasm +0 -0
  82. package/test/debug/http_server_e2e.js +31 -0
  83. package/test/debug/minwasm.js +52 -0
  84. package/test/debug/mupdf_debug.js +104 -0
  85. package/test/debug/wasm.js +122 -0
  86. package/test/debug/wasm_export_debug.js +64 -0
  87. package/test/debug/wasm_names.js +46 -0
  88. package/test/global_imports.wasm +0 -0
  89. package/test/import_func.wasm +0 -0
  90. package/test/imports.wasm +0 -0
  91. package/test/run.js +91 -0
  92. package/test/run.ts +96 -0
  93. package/test/sjlj.wasm +0 -0
  94. package/test/test_basic.js +7 -0
  95. package/test/test_basic.ts +9 -0
  96. package/test/test_basic_fetch.js +34 -0
  97. package/test/test_basic_fetch.ts +33 -0
  98. package/test/test_brotli.js +48 -0
  99. package/test/test_brotli.ts +52 -0
  100. package/test/test_fetch_cache.js +169 -0
  101. package/test/test_fetch_cache.ts +187 -0
  102. package/test/test_ffi.js +157 -0
  103. package/test/test_ffi.ts +174 -0
  104. package/test/test_frame_encoding.js +128 -0
  105. package/test/test_frame_encoding.ts +132 -0
  106. package/test/test_helper.js +84 -0
  107. package/test/test_helper.ts +80 -0
  108. package/test/test_http_import.js +78 -0
  109. package/test/test_http_import.ts +74 -0
  110. package/test/test_http_server.js +142 -0
  111. package/test/test_http_server.ts +136 -0
  112. package/test/test_import_dir.js +4 -0
  113. package/test/test_import_dir.ts +5 -0
  114. package/test/test_ipv6.js +134 -0
  115. package/test/test_ipv6.ts +129 -0
  116. package/test/test_jsx.js +1314 -0
  117. package/test/test_meta_url.js +3 -0
  118. package/test/test_meta_url.ts +4 -0
  119. package/test/test_mupdf_render.js +69 -0
  120. package/test/test_mupdf_render.ts +74 -0
  121. package/test/test_mupdf_twice.js +77 -0
  122. package/test/test_mupdf_twice.ts +81 -0
  123. package/test/test_mupdf_wasm.js +33 -0
  124. package/test/test_mupdf_wasm.ts +30 -0
  125. package/test/test_net_event.js +57 -0
  126. package/test/test_net_event.ts +55 -0
  127. package/test/test_net_fetch.js +259 -0
  128. package/test/test_net_fetch.ts +238 -0
  129. package/test/test_net_websocket.js +180 -0
  130. package/test/test_net_websocket.ts +164 -0
  131. package/test/test_polyfill.js +144 -0
  132. package/test/test_polyfill.ts +159 -0
  133. package/test/test_react_complex.js +17440 -0
  134. package/test/test_react_counter.js +17331 -0
  135. package/test/test_react_flex.js +17643 -0
  136. package/test/test_react_hidden.js +17336 -0
  137. package/test/test_react_listview_link.js +17371 -0
  138. package/test/test_react_render.js +17326 -0
  139. package/test/test_url.js +173 -0
  140. package/test/test_url.ts +183 -0
  141. package/test/test_wasm_basic.js +85 -0
  142. package/test/test_wasm_basic.ts +73 -0
  143. package/test/test_wasm_import_global.js +41 -0
  144. package/test/test_wasm_import_global.ts +45 -0
  145. package/test/test_wasm_sjlj.js +145 -0
  146. package/test/test_wasm_sjlj.ts +125 -0
  147. package/test/test_wasm_types.js +97 -0
  148. package/test/test_wasm_types.ts +124 -0
  149. package/test/test_worker.js +21 -0
  150. package/test/test_worker.ts +24 -0
  151. package/test/test_worker_fetch.js +26 -0
  152. package/test/test_worker_fetch.ts +27 -0
  153. package/test/test_worker_net.js +51 -0
  154. package/test/test_worker_net.ts +54 -0
  155. package/test/test_worker_wasm_concurrent.js +44 -0
  156. package/test/test_worker_wasm_concurrent.ts +45 -0
  157. package/test/types.wasm +0 -0
  158. package/test/worker_fetch.js +13 -0
  159. package/test/worker_fetch.ts +13 -0
  160. package/test/worker_helper.js +1 -0
  161. package/test/worker_helper.ts +1 -0
  162. package/test/worker_http.js +18 -0
  163. package/test/worker_http.ts +20 -0
  164. package/test/worker_import_lib.js +12 -0
  165. package/test/worker_import_lib.ts +13 -0
  166. package/test/worker_module.js +16 -0
  167. package/test/worker_module.ts +17 -0
  168. package/test/worker_wasm_concurrent.js +22 -0
  169. package/test/worker_wasm_concurrent.ts +22 -0
  170. package/tsconfig.json +27 -0
  171. package/vendor/mupdf-wasm/mupdf-wasm.d.ts +571 -0
  172. package/vendor/mupdf-wasm/mupdf-wasm.js +2749 -0
  173. package/vendor/mupdf-wasm/mupdf-wasm.wasm +0 -0
  174. package/vendor/mupdf-wasm/mupdf.d.ts +939 -0
  175. package/vendor/mupdf-wasm/mupdf.js +3317 -0
@@ -0,0 +1,111 @@
1
+ export type JustifyContent = 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly'
2
+
3
+ export interface FlexStyle {
4
+ flexDirection?: 'row' | 'column'
5
+ justifyContent?: JustifyContent
6
+ alignItems?: 'flex-start' | 'flex-end' | 'center' | 'stretch'
7
+ gap?: number
8
+ padding?: number
9
+ paddingTop?: number
10
+ paddingRight?: number
11
+ paddingBottom?: number
12
+ paddingLeft?: number
13
+ }
14
+
15
+ interface ChildStyle {
16
+ width?: number | 'auto'; height?: number | 'auto'
17
+ flexGrow?: number
18
+ alignSelf?: 'auto' | 'flex-start' | 'flex-end' | 'center' | 'stretch'
19
+ }
20
+
21
+ interface ChildInfo {
22
+ style: ChildStyle
23
+ }
24
+
25
+ export interface LayoutResult {
26
+ x: number; y: number; width: number; height: number
27
+ }
28
+
29
+ const DEFAULT_W = 100
30
+ const DEFAULT_H = 30
31
+
32
+ export function calculateFlexLayout(
33
+ flex: FlexStyle,
34
+ parentW: number,
35
+ parentH: number,
36
+ children: ChildInfo[]
37
+ ): LayoutResult[] {
38
+ const dir = flex.flexDirection || 'column'
39
+ const gap = flex.gap ?? 0
40
+ const justify = flex.justifyContent || 'flex-start'
41
+ const align = flex.alignItems || 'stretch'
42
+ const isRow = dir === 'row'
43
+ const n = children.length
44
+
45
+ const mainSize: 'w' | 'h' = isRow ? 'w' : 'h'
46
+ const crossSize: 'w' | 'h' = isRow ? 'h' : 'w'
47
+ const mainPos: 'x' | 'y' = isRow ? 'x' : 'y'
48
+ const crossPos: 'x' | 'y' = isRow ? 'y' : 'x'
49
+ const mainDim: 'width' | 'height' = isRow ? 'width' : 'height'
50
+ const crossDim: 'width' | 'height' = isRow ? 'height' : 'width'
51
+ const pt = flex.paddingTop ?? flex.padding ?? 0
52
+ const pr = flex.paddingRight ?? flex.padding ?? 0
53
+ const pb = flex.paddingBottom ?? flex.padding ?? 0
54
+ const pl = flex.paddingLeft ?? flex.padding ?? 0
55
+ const mainPadding = isRow ? pl + pr : pt + pb
56
+ const crossPadding = isRow ? pt + pb : pl + pr
57
+ const parentMain = (isRow ? parentW : parentH) - mainPadding
58
+ const parentCross = (isRow ? parentH : parentW) - crossPadding
59
+
60
+ const sizes = children.map(c => ({
61
+ w: typeof c.style.width === 'number' ? c.style.width : DEFAULT_W,
62
+ h: typeof c.style.height === 'number' ? c.style.height : DEFAULT_H,
63
+ }))
64
+
65
+ const flexGrows = children.map(c => c.style.flexGrow ?? 0)
66
+ const totalGrow = flexGrows.reduce((s, g) => s + g, 0)
67
+
68
+ const baseMain = sizes.reduce((s, c) => s + c[mainSize], 0)
69
+ let freeGrow = parentMain - baseMain - Math.max(0, n - 1) * gap
70
+ if (totalGrow > 0 && freeGrow > 0) {
71
+ for (let i = 0; i < n; i++) {
72
+ sizes[i]![mainSize] += freeGrow * flexGrows[i]! / totalGrow
73
+ }
74
+ }
75
+
76
+ const totalMain = sizes.reduce((s, c) => s + c[mainSize], 0)
77
+ const free = parentMain - totalMain - Math.max(0, n - 1) * gap
78
+ let offset = 0, extraGap = 0
79
+ if (justify === 'flex-end') offset = free
80
+ else if (justify === 'center') offset = Math.max(0, free / 2)
81
+ else if (justify === 'space-between' && n > 1) extraGap = free / (n - 1)
82
+ else if (justify === 'space-around') { extraGap = free / n; offset = extraGap / 2 }
83
+ else if (justify === 'space-evenly') { extraGap = free / (n + 1); offset = extraGap }
84
+
85
+ let cursor = offset
86
+ return sizes.map((sz, i) => {
87
+ const childCrossBase = sz[crossSize]
88
+ const childAlign = children[i]!.style.alignSelf ?? 'auto'
89
+ const effectiveAlign = childAlign === 'auto' ? align : childAlign
90
+ let childMain = sz[mainSize]
91
+ let childCross = childCrossBase
92
+ const res: LayoutResult = { x: 0, y: 0, width: 0, height: 0 }
93
+ res[mainPos] = cursor
94
+ res[crossPos] = 0
95
+ res[mainDim] = childMain
96
+ res[crossDim] = childCross
97
+ if (effectiveAlign === 'stretch') {
98
+ const hasExplicitCross = typeof children[i]!.style[crossDim] === 'number'
99
+ if (!hasExplicitCross) {
100
+ res[crossDim] = parentCross
101
+ res[crossPos] = 0
102
+ }
103
+ } else if (effectiveAlign === 'flex-end') {
104
+ res[crossPos] = parentCross - childCross
105
+ } else if (effectiveAlign === 'center') {
106
+ res[crossPos] = Math.max(0, (parentCross - childCross) / 2)
107
+ }
108
+ cursor += childMain + gap + extraGap
109
+ return res
110
+ })
111
+ }
@@ -0,0 +1,27 @@
1
+ import * as gui from 'gui';
2
+ export function applyProps(instance, newProps, _oldProps) {
3
+ const hwnd = instance.hwnd;
4
+ instance.props = newProps;
5
+ const textVal = 'text' in newProps ? newProps.text : (typeof newProps.children === 'string' ? newProps.children :
6
+ typeof newProps.children === 'number' ? String(newProps.children) : undefined);
7
+ if (textVal !== undefined) {
8
+ let cursor = -1;
9
+ if (instance.type === 'EDIT') {
10
+ const sel = gui.SendMessage(hwnd, 176 /* gui.EditMsg.GETSEL */, 0, 0);
11
+ cursor = sel >>> 16;
12
+ }
13
+ gui.SetWindowText(hwnd, textVal);
14
+ if (instance.type === 'EDIT' && cursor >= 0) {
15
+ gui.SendMessage(hwnd, 177 /* gui.EditMsg.SETSEL */, cursor, cursor);
16
+ }
17
+ }
18
+ if ('disabled' in newProps) {
19
+ gui.EnableWindow(hwnd, !newProps.disabled);
20
+ }
21
+ if ('hidden' in newProps) {
22
+ gui.ShowWindow(hwnd, newProps.hidden ? 0 /* gui.ShowWindowCmd.HIDE */ : 5 /* gui.ShowWindowCmd.SHOW */);
23
+ }
24
+ if ('visible' in newProps) {
25
+ gui.ShowWindow(hwnd, newProps.visible ? 5 /* gui.ShowWindowCmd.SHOW */ : 0 /* gui.ShowWindowCmd.HIDE */);
26
+ }
27
+ }
@@ -0,0 +1,36 @@
1
+ import * as gui from 'gui'
2
+ import type { Instance, Props } from './reconciler.js'
3
+
4
+ export function applyProps(
5
+ instance: Instance,
6
+ newProps: Props,
7
+ _oldProps: Props,
8
+ ) {
9
+ const hwnd = instance.hwnd!
10
+ instance.props = newProps
11
+
12
+ const textVal = 'text' in newProps ? newProps.text : (
13
+ typeof newProps.children === 'string' ? newProps.children :
14
+ typeof newProps.children === 'number' ? String(newProps.children) : undefined
15
+ )
16
+ if (textVal !== undefined) {
17
+ let cursor = -1
18
+ if (instance.type === 'EDIT') {
19
+ const sel = gui.SendMessage(hwnd, gui.EditMsg.GETSEL, 0, 0)
20
+ cursor = sel >>> 16
21
+ }
22
+ gui.SetWindowText(hwnd, textVal)
23
+ if (instance.type === 'EDIT' && cursor >= 0) {
24
+ gui.SendMessage(hwnd, gui.EditMsg.SETSEL, cursor, cursor)
25
+ }
26
+ }
27
+ if ('disabled' in newProps) {
28
+ gui.EnableWindow(hwnd, !newProps.disabled)
29
+ }
30
+ if ('hidden' in newProps) {
31
+ gui.ShowWindow(hwnd, newProps.hidden ? gui.ShowWindowCmd.HIDE : gui.ShowWindowCmd.SHOW)
32
+ }
33
+ if ('visible' in newProps) {
34
+ gui.ShowWindow(hwnd, newProps.visible ? gui.ShowWindowCmd.SHOW : gui.ShowWindowCmd.HIDE)
35
+ }
36
+ }
@@ -0,0 +1,333 @@
1
+ import ReactReconciler from 'react-reconciler';
2
+ import { createContext } from 'react';
3
+ import { DefaultEventPriority, NoEventPriority } from 'react-reconciler/constants';
4
+ import * as gui from 'gui';
5
+ import * as os from 'os';
6
+ import { applyProps } from './props.js';
7
+ import { calculateFlexLayout } from './layout.js';
8
+ import { getButtonIdealSize, measureTextForHwnd } from '../text-measure.js';
9
+ const dpiFont = gui.CreateSystemDpiFont();
10
+ export const scaleFactor = gui.GetScaleFactor();
11
+ const DELAYED_CONTROLS = new Set(['SysListView32', 'SysTreeView32', 'LISTBOX', 'COMBOBOX', 'msctls_trackbar32', 'SysDateTimePick32', 'SysLink']);
12
+ function isDelayedControl(winClass) {
13
+ return DELAYED_CONTROLS.has(winClass);
14
+ }
15
+ function setupWindowProc(instance, hwnd) {
16
+ instance.hwnd = hwnd;
17
+ const oldProc = gui.GetWindowLongPtr(hwnd, -4 /* gui.Gwlp.WNDPROC */);
18
+ instancesByHwnd.set(hwnd, instance);
19
+ gui.SetWindowProc(hwnd, (hwnd, msg, wParam, lParam) => {
20
+ const ev = instance.props.onEvent;
21
+ if (!ev)
22
+ return gui.CallWindowProc(oldProc, hwnd, msg, wParam, lParam);
23
+ if (typeof ev === 'object') {
24
+ return ev.fn({ hwnd, msg, wParam, lParam, callOldWndProc: () => gui.CallWindowProc(oldProc, hwnd, msg, wParam, lParam) });
25
+ }
26
+ const result = gui.CallWindowProc(oldProc, hwnd, msg, wParam, lParam);
27
+ const override = ev({ hwnd, msg, wParam, lParam });
28
+ return typeof override === 'number' && Number.isInteger(override) ? override : result;
29
+ });
30
+ }
31
+ function ensureChildWindow(child, parentHwnd) {
32
+ if (child.hwnd !== null)
33
+ return child.hwnd;
34
+ const sty = child.props.style || {};
35
+ const ws = ((child.props.ws ?? 0) | 1073741824 /* gui.WindowStyle.CHILD */) & ~268435456 /* gui.WindowStyle.VISIBLE */;
36
+ const hwnd = gui.CreateWindow(child.type, child.props.text || '', ws, 0, 0, (typeof sty.width === 'number' ? sty.width : 100) * scaleFactor, (typeof sty.height === 'number' ? sty.height : 30) * scaleFactor, parentHwnd, null);
37
+ if (dpiFont)
38
+ gui.SendMessage(hwnd, 48 /* gui.WmMsg.SETFONT */, dpiFont, 1);
39
+ setupWindowProc(child, hwnd);
40
+ return hwnd;
41
+ }
42
+ export const instancesByHwnd = new Map();
43
+ function runFlexLayout(inst) {
44
+ const children = inst.children;
45
+ if (children.length === 0)
46
+ return;
47
+ const s = inst.props.style;
48
+ const flex = s || {};
49
+ const visible = children.filter(c => !c.props.hidden);
50
+ if (visible.length === 0)
51
+ return;
52
+ const rect = gui.GetClientRect(inst.hwnd);
53
+ if (!rect) {
54
+ console.log('flex: no rect for', inst.hwnd, inst.type);
55
+ return;
56
+ }
57
+ const pw = (rect.right - rect.left) / scaleFactor;
58
+ const ph = (rect.bottom - rect.top) / scaleFactor;
59
+ if (pw <= 0 || ph <= 0) {
60
+ console.log('flex: zero size for', inst.hwnd, inst.type, pw, ph);
61
+ return;
62
+ }
63
+ const autoSizes = new Map();
64
+ for (const child of visible) {
65
+ const sty = child.props.style || {};
66
+ if (!child.hwnd)
67
+ continue;
68
+ if (child.type === 'BUTTON' && (sty.width === 'auto' || sty.height === 'auto')) {
69
+ const ideal = getButtonIdealSize(child.hwnd);
70
+ autoSizes.set(child, { w: ideal.width / scaleFactor, h: ideal.height / scaleFactor });
71
+ }
72
+ else if (child.type === 'STATIC' && (sty.width === 'auto' || sty.height === 'auto') && child.props.text) {
73
+ const measured = measureTextForHwnd(child.hwnd, child.props.text);
74
+ autoSizes.set(child, { w: measured.width / scaleFactor, h: measured.height / scaleFactor });
75
+ }
76
+ }
77
+ const results = calculateFlexLayout(flex, pw, ph, visible.map(c => {
78
+ const sty = c.props.style || {};
79
+ const auto = autoSizes.get(c);
80
+ return {
81
+ style: {
82
+ ...sty,
83
+ width: auto ? (sty.width === 'auto' ? auto.w : sty.width) : sty.width,
84
+ height: auto ? (sty.height === 'auto' ? auto.h : sty.height) : sty.height,
85
+ }
86
+ };
87
+ }));
88
+ const pl = flex.paddingLeft ?? flex.padding ?? 0;
89
+ const pt = flex.paddingTop ?? flex.padding ?? 0;
90
+ for (let i = 0; i < results.length; i++) {
91
+ const r = results[i];
92
+ const child = visible[i];
93
+ const lr = child.lastRect;
94
+ if (lr && lr.x === r.x && lr.y === r.y && lr.w === r.width && lr.h === r.height)
95
+ continue;
96
+ console.log('flex: set', child.type, child.hwnd, 'to', r.x, r.y, r.width, r.height);
97
+ gui.SetWindowPos(child.hwnd, 0 /* gui.SetWindowPosHwnd.TOP */, (r.x + pl) * scaleFactor, (r.y + pt) * scaleFactor, r.width * scaleFactor, r.height * scaleFactor, 64 /* gui.SetWindowPosFlag.SWP_SHOWWINDOW */);
98
+ gui.InvalidateRect(child.hwnd, null, true);
99
+ child.lastRect = { x: r.x, y: r.y, w: r.width, h: r.height };
100
+ }
101
+ for (const c of children)
102
+ runFlexLayout(c);
103
+ }
104
+ export function forceFlexLayout(hwnd) {
105
+ const inst = instancesByHwnd.get(hwnd);
106
+ if (inst)
107
+ runFlexLayout(inst);
108
+ }
109
+ let currentUpdatePriority = NoEventPriority;
110
+ const hostConfig = {
111
+ // Core methods
112
+ createInstance(type, props, rootContainer) {
113
+ if (DEBUG)
114
+ console.log('[reconciler] createInstance called:', type, 'class=' + props.type);
115
+ const winClass = props.type;
116
+ if (isDelayedControl(winClass)) {
117
+ if (DEBUG)
118
+ console.log('[reconciler] delayed control, skipping CreateWindow:', winClass);
119
+ return { hwnd: null, type: winClass, props, children: [] };
120
+ }
121
+ if (winClass === 'STATIC' && props.text !== undefined && props.children !== undefined && props.children !== null) {
122
+ throw new Error('[STATIC] text and children are mutually exclusive');
123
+ }
124
+ const sty = props.style || {};
125
+ // reconciler 创建的都是子窗口,确保 WS_CHILD 避免定位异常;创建时不带 WS_VISIBLE 防止 (0,0) 闪烁
126
+ const ws = ((props.ws ?? 0) | 1073741824 /* gui.WindowStyle.CHILD */) & ~268435456 /* gui.WindowStyle.VISIBLE */;
127
+ if (DEBUG)
128
+ console.log('[reconciler] CreateWindow args:', winClass, props.text ?? '', ws, 0, 0, sty.width ?? 100, sty.height ?? 30, rootContainer);
129
+ const hwnd = gui.CreateWindow(winClass, props.text ?? '', ws, 0, 0, (typeof sty.width === 'number' ? sty.width : 100) * scaleFactor, (typeof sty.height === 'number' ? sty.height : 30) * scaleFactor, rootContainer, null);
130
+ if (dpiFont)
131
+ gui.SendMessage(hwnd, 48 /* gui.WmMsg.SETFONT */, dpiFont, 1);
132
+ if (DEBUG)
133
+ console.log('[reconciler] createInstance hwnd result:', hwnd, 'null?', hwnd === null);
134
+ const instance = { hwnd, type: winClass, props, children: [] };
135
+ setupWindowProc(instance, hwnd);
136
+ return instance;
137
+ },
138
+ createTextInstance(_text, _rootContainer) {
139
+ throw new Error('should never be called (shouldSetTextContent=true)');
140
+ },
141
+ appendInitialChild(parent, child) {
142
+ if (child.hwnd === null) {
143
+ ensureChildWindow(child, parent.hwnd);
144
+ }
145
+ else {
146
+ gui.SetParent(child.hwnd, parent.hwnd);
147
+ }
148
+ if (DEBUG)
149
+ console.log('[reconciler] appendInitialChild parent:', parent.hwnd, 'child:', child.hwnd);
150
+ parent.children.push(child);
151
+ },
152
+ appendChild(parent, child) {
153
+ if (child.hwnd === null) {
154
+ ensureChildWindow(child, parent.hwnd);
155
+ }
156
+ else {
157
+ gui.SetParent(child.hwnd, parent.hwnd);
158
+ }
159
+ if (DEBUG)
160
+ console.log('[reconciler] appendChild parent:', parent.hwnd, 'child:', child.hwnd);
161
+ parent.children.push(child);
162
+ },
163
+ appendChildToContainer(container, child) {
164
+ if (child.hwnd === null) {
165
+ ensureChildWindow(child, container);
166
+ }
167
+ else {
168
+ gui.SetParent(child.hwnd, container);
169
+ }
170
+ const rootInst = instancesByHwnd.get(container);
171
+ if (rootInst)
172
+ rootInst.children.push(child);
173
+ if (DEBUG)
174
+ console.log('[reconciler] appendChildToContainer container:', container, 'child hwnd:', child.hwnd);
175
+ },
176
+ insertBefore(parent, child, beforeChild) {
177
+ if (child.hwnd === null) {
178
+ ensureChildWindow(child, parent.hwnd);
179
+ }
180
+ else {
181
+ gui.SetParent(child.hwnd, parent.hwnd);
182
+ }
183
+ if (DEBUG)
184
+ console.log('[reconciler] insertBefore parent:', parent.hwnd, 'child:', child.hwnd, 'before:', beforeChild.hwnd);
185
+ const idx = parent.children.indexOf(beforeChild);
186
+ if (idx >= 0)
187
+ parent.children.splice(idx, 0, child);
188
+ },
189
+ insertInContainerBefore(container, child, _before) {
190
+ if (child.hwnd === null) {
191
+ ensureChildWindow(child, container);
192
+ }
193
+ else {
194
+ gui.SetParent(child.hwnd, container);
195
+ }
196
+ const rootInst = instancesByHwnd.get(container);
197
+ if (!rootInst)
198
+ throw new Error('insertInContainerBefore: no root instance for container');
199
+ const idx = rootInst.children.indexOf(_before);
200
+ if (idx >= 0)
201
+ rootInst.children.splice(idx, 0, child);
202
+ else
203
+ rootInst.children.push(child);
204
+ if (DEBUG)
205
+ console.log('[reconciler] insertInContainerBefore container:', container, 'child hwnd:', child.hwnd);
206
+ },
207
+ removeChild(parent, child) {
208
+ if (DEBUG)
209
+ console.log('[reconciler] removeChild parent:', parent.hwnd, 'child:', child.hwnd, 'type:', child.type);
210
+ if (child.hwnd !== null)
211
+ gui.DestroyWindow(child.hwnd);
212
+ const idx = parent.children.indexOf(child);
213
+ if (idx >= 0)
214
+ parent.children.splice(idx, 1);
215
+ },
216
+ removeChildFromContainer(container, child) {
217
+ if (child.hwnd === null)
218
+ return;
219
+ if (DEBUG)
220
+ console.log('[reconciler] removeChildFromContainer container:', container, 'child hwnd:', child.hwnd);
221
+ gui.DestroyWindow(child.hwnd);
222
+ const rootInst = instancesByHwnd.get(container);
223
+ if (rootInst) {
224
+ const idx = rootInst.children.indexOf(child);
225
+ if (idx >= 0)
226
+ rootInst.children.splice(idx, 1);
227
+ }
228
+ },
229
+ commitUpdate(instance, _type, oldProps, newProps, _internalHandle) {
230
+ applyProps(instance, newProps, oldProps);
231
+ },
232
+ commitMount(_instance, _type, _props, _internal) { },
233
+ finalizeInitialChildren(instance, type, props) {
234
+ if (DEBUG)
235
+ console.log('[reconciler] finalizeInitialChildren instance:', instance.hwnd, 'props:', JSON.stringify(props));
236
+ applyProps(instance, props, { type });
237
+ return false;
238
+ },
239
+ resetAfterCommit(containerInfo) {
240
+ if (DEBUG)
241
+ console.log('[reconciler] resetAfterCommit');
242
+ const rootInst = instancesByHwnd.get(containerInfo);
243
+ if (!rootInst)
244
+ throw new Error('resetAfterCommit: no root instance for container');
245
+ runFlexLayout(rootInst);
246
+ },
247
+ resetTextContent(_instance) { },
248
+ commitTextUpdate(instance, _oldText, newText) {
249
+ gui.SetWindowText(instance.hwnd, newText);
250
+ },
251
+ hideInstance(instance) {
252
+ if (instance.hwnd !== null)
253
+ gui.ShowWindow(instance.hwnd, 0 /* gui.ShowWindowCmd.HIDE */);
254
+ },
255
+ hideTextInstance(instance) {
256
+ if (instance.hwnd !== null)
257
+ gui.ShowWindow(instance.hwnd, 0 /* gui.ShowWindowCmd.HIDE */);
258
+ },
259
+ unhideInstance(instance, _props) {
260
+ if (instance.hwnd !== null)
261
+ gui.ShowWindow(instance.hwnd, 5 /* gui.ShowWindowCmd.SHOW */);
262
+ },
263
+ unhideTextInstance(instance, _text) {
264
+ if (instance.hwnd !== null)
265
+ gui.ShowWindow(instance.hwnd, 5 /* gui.ShowWindowCmd.SHOW */);
266
+ },
267
+ shouldSetTextContent(_type, props) {
268
+ return typeof props.children === 'string' || typeof props.children === 'number';
269
+ },
270
+ getPublicInstance(instance) {
271
+ return instance.hwnd;
272
+ },
273
+ getRootHostContext(_rootContainer) {
274
+ return {};
275
+ },
276
+ getChildHostContext(_parentContext, _type) {
277
+ return {};
278
+ },
279
+ prepareForCommit(_containerInfo) {
280
+ return null;
281
+ },
282
+ preparePortalMount(_containerInfo) { },
283
+ clearContainer(_container) {
284
+ // 不需要清理,React reconciler 会通过 removeChildFromContainer 来移除子节点
285
+ },
286
+ scheduleTimeout(fn, delay) {
287
+ return os.setTimeout(fn, delay);
288
+ },
289
+ cancelTimeout(id) {
290
+ os.clearTimeout?.(id);
291
+ },
292
+ noTimeout: -1,
293
+ supportsMutation: true,
294
+ supportsPersistence: false,
295
+ supportsHydration: false,
296
+ isPrimaryRenderer: true,
297
+ supportsMicrotasks: false,
298
+ setCurrentUpdatePriority(p) { currentUpdatePriority = p; },
299
+ getCurrentUpdatePriority() { return currentUpdatePriority; },
300
+ resolveUpdatePriority() { return DefaultEventPriority; },
301
+ resolveEventType() { return null; },
302
+ resolveEventTimeStamp() { return Date.now(); },
303
+ trackSchedulerEvent() { },
304
+ shouldAttemptEagerTransition() { return false; },
305
+ detachDeletedInstance(instance) {
306
+ if (DEBUG)
307
+ console.log('[reconciler] detachDeletedInstance hwnd:', instance.hwnd, 'type:', instance.type);
308
+ if (instance.hwnd !== null)
309
+ instancesByHwnd.delete(instance.hwnd);
310
+ },
311
+ // 添加必需方法
312
+ getInstanceFromNode(_node) { return null; },
313
+ beforeActiveInstanceBlur() { },
314
+ afterActiveInstanceBlur() { },
315
+ prepareScopeUpdate(_scopeInstance, _instance) { },
316
+ getInstanceFromScope(_scopeInstance) { return null; },
317
+ // Suspense/Concurrent Mode 支持
318
+ maySuspendCommit() { return false; },
319
+ requestPostPaintCallback() { },
320
+ preloadInstance() { return true; },
321
+ startSuspendingCommit() { },
322
+ suspendInstance() { },
323
+ waitForCommitToBeReady() { return null; },
324
+ rendererVersion: '0.1.0',
325
+ rendererPackageName: 'react-qw',
326
+ NotPendingTransition: null,
327
+ // @types/react.Context<T> 与 @types/react-reconciler.ReactContext<T> 是同一运行时对象
328
+ // 的两个不兼容类型定义(ReactContext 多 _currentValue 等字段),此 cast 不可避免
329
+ HostTransitionContext: createContext(null),
330
+ resetFormInstance(_form) { },
331
+ };
332
+ const reconciler = ReactReconciler(hostConfig);
333
+ export default reconciler;