@effindomv2/fui-as 0.1.37 → 0.1.39

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 (118) hide show
  1. package/browser/routed-app-conventions.ts +1 -1
  2. package/browser/src/common-harness.ts +1 -27
  3. package/browser/src/index.ts +19 -26
  4. package/browser/src/routed-harness.ts +1 -194
  5. package/browser/src/shared-browser.ts +6 -0
  6. package/package.json +23 -13
  7. package/scripts/build.sh +47 -46
  8. package/scripts/framework-host-services.ts +1 -1
  9. package/scripts/generate-host-events.ts +15 -241
  10. package/scripts/generate-host-services.ts +13 -261
  11. package/scripts/hostgen/assemblyscript-host-events.ts +165 -0
  12. package/scripts/hostgen/assemblyscript-host-services.ts +207 -0
  13. package/scripts/hostgen/common.ts +73 -0
  14. package/scripts/hostgen/registry.ts +159 -0
  15. package/src/Fui.ts +65 -5
  16. package/src/FuiExports.ts +12 -1
  17. package/src/bindings/ui.ts +157 -8
  18. package/src/controls/Button.ts +98 -71
  19. package/src/controls/Checkbox.ts +23 -21
  20. package/src/controls/ComboBox.ts +1201 -0
  21. package/src/controls/ContextMenu.ts +279 -165
  22. package/src/controls/Dialog.ts +91 -17
  23. package/src/controls/Dropdown.ts +101 -333
  24. package/src/controls/NavLink.ts +29 -6
  25. package/src/controls/Popup.ts +10 -5
  26. package/src/controls/ProgressBar.ts +1 -2
  27. package/src/controls/RadioButton.ts +34 -13
  28. package/src/controls/RadioGroup.ts +32 -19
  29. package/src/controls/Slider.ts +21 -23
  30. package/src/controls/Switch.ts +34 -17
  31. package/src/controls/index.ts +2 -1
  32. package/src/controls/internal/ButtonPresenter.ts +5 -2
  33. package/src/controls/internal/CheckboxIndicatorPresenter.ts +10 -10
  34. package/src/controls/internal/DropdownChevronPresenter.ts +3 -3
  35. package/src/controls/internal/DropdownFieldPresenter.ts +6 -6
  36. package/src/controls/internal/DropdownOptionRowPresenter.ts +5 -4
  37. package/src/controls/internal/PressableLabeledControl.ts +4 -7
  38. package/src/controls/internal/RadioIndicatorPresenter.ts +7 -7
  39. package/src/controls/internal/SelectablePopupList.ts +463 -0
  40. package/src/controls/internal/SliderPresenter.ts +5 -5
  41. package/src/controls/internal/SwitchIndicatorPresenter.ts +63 -17
  42. package/src/controls/internal/TextInputCore.ts +307 -41
  43. package/src/controls/internal/TextInputPresenter.ts +2 -2
  44. package/src/controls/templating.ts +10 -0
  45. package/src/core/Application.ts +17 -8
  46. package/src/core/Bitmap.ts +192 -3
  47. package/src/core/ContextMenuManager.ts +128 -44
  48. package/src/core/DebugApi.ts +17 -2
  49. package/src/core/DragGesture.ts +8 -7
  50. package/src/core/EventRouter.ts +332 -12
  51. package/src/core/Fetch.ts +15 -7
  52. package/src/core/FetchFfi.ts +1 -15
  53. package/src/core/File.ts +75 -34
  54. package/src/core/FocusAdornerManager.ts +3 -3
  55. package/src/core/FocusVisibility.ts +7 -0
  56. package/src/core/FrameScheduler.ts +104 -5
  57. package/src/core/ImageSampling.ts +43 -0
  58. package/src/core/Logger.ts +2 -0
  59. package/src/core/MobileTextSelectionToolbarManager.ts +901 -0
  60. package/src/core/Node.ts +1039 -97
  61. package/src/core/Platform.ts +16 -16
  62. package/src/core/SelectionHandleAdornerManager.ts +697 -0
  63. package/src/core/Theme.ts +34 -20
  64. package/src/core/ToolTipManager.ts +3 -3
  65. package/src/core/Typography.ts +411 -19
  66. package/src/core/Worker.ts +69 -33
  67. package/src/core/event_exports.ts +123 -5
  68. package/src/core/ffi.ts +5 -865
  69. package/src/core/generated/FetchHostAbi.ts +10 -0
  70. package/src/core/generated/FrameworkHostServices.ts +1 -1
  71. package/src/core/generated/HostAbi.ts +220 -0
  72. package/src/core/generated/UiAbi.ts +356 -0
  73. package/src/core/generated/UiEnums.ts +186 -0
  74. package/src/drawing/DrawCallback.ts +17 -1
  75. package/src/drawing/DrawContext.ts +140 -32
  76. package/src/nodes/CustomDrawable.ts +8 -1
  77. package/src/nodes/FlexBox.ts +40 -11
  78. package/src/nodes/Grid.ts +5 -5
  79. package/src/nodes/Image.ts +20 -1
  80. package/src/nodes/RichText.ts +36 -25
  81. package/src/nodes/ScrollBar.ts +2 -1
  82. package/src/nodes/ScrollBox.ts +7 -2
  83. package/src/nodes/ScrollView.ts +17 -6
  84. package/src/nodes/Svg.ts +18 -1
  85. package/src/nodes/Text.ts +1 -1
  86. package/src/nodes/TextCore.ts +115 -20
  87. package/src/nodes/index.ts +1 -1
  88. package/src/text/DynamicTextLayout.ts +275 -0
  89. package/src/text/TextLayout.ts +308 -0
  90. package/src/worker/Worker.ts +3 -36
  91. package/src/worker/WorkerJob.ts +5 -9
  92. package/src/worker/ffi.ts +0 -6
  93. package/browser/src/common-harness/host-imports.ts +0 -447
  94. package/browser/src/common-harness/interop.ts +0 -39
  95. package/browser/src/common-harness/managed-harness-bitmap-host.ts +0 -92
  96. package/browser/src/common-harness/managed-harness-canvas-host.ts +0 -72
  97. package/browser/src/common-harness/managed-harness-fetch-host.ts +0 -201
  98. package/browser/src/common-harness/managed-harness-file-host.ts +0 -1066
  99. package/browser/src/common-harness/managed-harness-file-payloads.ts +0 -143
  100. package/browser/src/common-harness/managed-harness-file-types.ts +0 -71
  101. package/browser/src/common-harness/managed-harness-session.ts +0 -15
  102. package/browser/src/common-harness/managed-harness.ts +0 -1472
  103. package/browser/src/common-harness/managed-history.ts +0 -168
  104. package/browser/src/common-harness/persisted-restore-policy.ts +0 -50
  105. package/browser/src/common-harness/persisted-ui-state-controller.ts +0 -309
  106. package/browser/src/common-harness/text-session-bridge.ts +0 -452
  107. package/browser/src/common-harness/types.ts +0 -206
  108. package/browser/src/common-harness/ui-chrome.ts +0 -206
  109. package/browser/src/common-harness/ui-imports.ts +0 -559
  110. package/browser/src/common-harness/wasm-module-cache.ts +0 -47
  111. package/browser/src/file-processing-worker.ts +0 -89
  112. package/browser/src/host-events.ts +0 -118
  113. package/browser/src/host-services.ts +0 -483
  114. package/browser/src/persisted-ui-state.ts +0 -206
  115. package/browser/src/routed-app-conventions.ts +0 -292
  116. package/browser/src/worker-bootstrap.ts +0 -916
  117. package/browser/src/worker-manager.ts +0 -234
  118. package/browser/src/worker-types.ts +0 -68
@@ -1,452 +0,0 @@
1
- import type { BridgeRuntime, WasmHandleLike } from '@effindomv2/runtime';
2
-
3
- import { currentInteractionTimeMs, toBigIntHandle, toNumberHandle, zeroPointer, normalizePointer, type AppHandleLike } from './interop';
4
-
5
- const decoder = new TextDecoder();
6
- const encoder = new TextEncoder();
7
- const TEXTBOX_HARD_CLAMP_MAX_CODEPOINTS = 10000;
8
-
9
- interface TextClampRange {
10
- readonly start: number;
11
- readonly end: number;
12
- }
13
-
14
- function advanceCodeUnitIndex(text: string, index: number): number {
15
- const codePoint = text.codePointAt(index) ?? 0;
16
- return index + (codePoint > 0xffff ? 2 : 1);
17
- }
18
-
19
- function isLineBreakCodeUnit(text: string, index: number): boolean {
20
- if (index < 0 || index >= text.length) {
21
- return false;
22
- }
23
- const codeUnit = text.charCodeAt(index);
24
- return codeUnit === 0x0a || codeUnit === 0x0d;
25
- }
26
-
27
- function collectTextboxHardLineClampRanges(text: string): readonly TextClampRange[] {
28
- const ranges: TextClampRange[] = [];
29
- let index = 0;
30
- while (index < text.length) {
31
- let lineCapEnd = index;
32
- let lineEnd = index;
33
- let codePointCount = 0;
34
- while (lineEnd < text.length && !isLineBreakCodeUnit(text, lineEnd)) {
35
- const next = advanceCodeUnitIndex(text, lineEnd);
36
- if (codePointCount < TEXTBOX_HARD_CLAMP_MAX_CODEPOINTS) {
37
- lineCapEnd = next;
38
- }
39
- codePointCount += 1;
40
- lineEnd = next;
41
- }
42
- if (codePointCount > TEXTBOX_HARD_CLAMP_MAX_CODEPOINTS) {
43
- ranges.push({ start: lineCapEnd, end: lineEnd });
44
- }
45
- if (lineEnd >= text.length) {
46
- break;
47
- }
48
- if (text.charCodeAt(lineEnd) === 0x0d && lineEnd + 1 < text.length && text.charCodeAt(lineEnd + 1) === 0x0a) {
49
- index = lineEnd + 2;
50
- } else {
51
- index = lineEnd + 1;
52
- }
53
- }
54
- return ranges;
55
- }
56
-
57
- function mapClampedTextIndex(index: number, ranges: readonly TextClampRange[]): number {
58
- const clampedIndex = Math.max(0, index);
59
- let removedBefore = 0;
60
- for (const range of ranges) {
61
- if (clampedIndex <= range.start) {
62
- break;
63
- }
64
- if (clampedIndex < range.end) {
65
- return range.start - removedBefore;
66
- }
67
- removedBefore += range.end - range.start;
68
- }
69
- return clampedIndex - removedBefore;
70
- }
71
-
72
- function clampTextboxHardLines(text: string): {
73
- readonly text: string;
74
- mapIndex(index: number): number;
75
- } {
76
- const ranges = collectTextboxHardLineClampRanges(text);
77
- if (ranges.length === 0) {
78
- return {
79
- text,
80
- mapIndex: (index: number) => Math.max(0, Math.min(index, text.length)),
81
- };
82
- }
83
- let result = '';
84
- let cursor = 0;
85
- for (const range of ranges) {
86
- result += text.slice(cursor, range.start);
87
- cursor = range.end;
88
- }
89
- result += text.slice(cursor);
90
- return {
91
- text: result,
92
- mapIndex: (index: number) => mapClampedTextIndex(index, ranges),
93
- };
94
- }
95
-
96
- function computeReplacementEdit(previousText: string, nextText: string): {
97
- readonly start: number;
98
- readonly end: number;
99
- readonly insertedText: string;
100
- } | null {
101
- if (previousText === nextText) {
102
- return null;
103
- }
104
-
105
- const sharedPrefixLimit = Math.min(previousText.length, nextText.length);
106
- let prefix = 0;
107
- while (prefix < sharedPrefixLimit && previousText.charCodeAt(prefix) === nextText.charCodeAt(prefix)) {
108
- prefix += 1;
109
- }
110
-
111
- let suffix = 0;
112
- while (
113
- suffix < (previousText.length - prefix) &&
114
- suffix < (nextText.length - prefix) &&
115
- previousText.charCodeAt(previousText.length - suffix - 1) === nextText.charCodeAt(nextText.length - suffix - 1)
116
- ) {
117
- suffix += 1;
118
- }
119
-
120
- return {
121
- start: prefix,
122
- end: previousText.length - suffix,
123
- insertedText: nextText.slice(prefix, nextText.length - suffix),
124
- };
125
- }
126
-
127
- function applyReplacementEdit(text: string, start: number, end: number, insertedText: string): string {
128
- const clampedStart = Math.max(0, Math.min(start, text.length));
129
- const clampedEnd = Math.max(clampedStart, Math.min(end, text.length));
130
- return `${text.slice(0, clampedStart)}${insertedText}${text.slice(clampedEnd)}`;
131
- }
132
-
133
- function getHiddenTextEditor(): HTMLInputElement | HTMLTextAreaElement | null {
134
- const activeElement = document.activeElement;
135
- if (
136
- (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) &&
137
- activeElement.dataset.effindomHiddenEditor === 'true'
138
- ) {
139
- return activeElement;
140
- }
141
- const editor = document.querySelector('input[data-effindom-hidden-editor="true"], textarea[data-effindom-hidden-editor="true"]');
142
- return editor instanceof HTMLInputElement || editor instanceof HTMLTextAreaElement ? editor : null;
143
- }
144
-
145
- export interface TextSessionLike {
146
- readonly memory: WebAssembly.Memory;
147
- readonly textBufferPtr: number;
148
- readonly textBufferSize: number;
149
- }
150
-
151
- interface FrozenTextSelectionSnapshot {
152
- readonly handleKey: string;
153
- readonly text: string;
154
- readonly start: number;
155
- readonly end: number;
156
- }
157
-
158
- export class TextSessionBridge {
159
- private readonly latestTextByHandle = new Map<string, string>();
160
- private readonly latestSelectionByHandle = new Map<string, { start: number; end: number }>();
161
- private frozenTextSelectionSnapshot: FrozenTextSelectionSnapshot | null = null;
162
-
163
- constructor(
164
- private readonly getRuntime: () => BridgeRuntime,
165
- private readonly getCurrentMemory: () => WebAssembly.Memory,
166
- private readonly queueHarnessFrame: () => void,
167
- ) {}
168
-
169
- clearState(): void {
170
- this.latestTextByHandle.clear();
171
- this.latestSelectionByHandle.clear();
172
- this.frozenTextSelectionSnapshot = null;
173
- }
174
-
175
- readAppUtf8(ptr: number, len: number): string {
176
- if (len === 0) {
177
- return '';
178
- }
179
- return decoder.decode(new Uint8Array(this.getCurrentMemory().buffer, ptr, len));
180
- }
181
-
182
- readAppFloats(ptr: number, count: number): Float32Array {
183
- if (count === 0) {
184
- return new Float32Array(0);
185
- }
186
- return new Float32Array(this.getCurrentMemory().buffer.slice(ptr, ptr + (count * 4)));
187
- }
188
-
189
- readAppBytes(ptr: number, len: number): Uint8Array {
190
- if (len === 0) {
191
- return new Uint8Array(0);
192
- }
193
- return new Uint8Array(this.getCurrentMemory().buffer.slice(ptr, ptr + len));
194
- }
195
-
196
- readAppTextParts(ptr: number, len: number): string[] {
197
- if (len === 0) {
198
- return [];
199
- }
200
- const source = new Uint8Array(this.getCurrentMemory().buffer, ptr, len);
201
- if (source.byteLength < 4) {
202
- throw new Error('Fetch request header payload was truncated.');
203
- }
204
- const dataView = new DataView(source.buffer, source.byteOffset, source.byteLength);
205
- let byteOffset = 0;
206
- const count = dataView.getUint32(byteOffset, true);
207
- byteOffset += 4;
208
- const values: string[] = [];
209
- for (let index = 0; index < count; index += 1) {
210
- if (byteOffset + 4 > source.byteLength) {
211
- throw new Error('Fetch request header length was truncated.');
212
- }
213
- const partLen = dataView.getUint32(byteOffset, true);
214
- byteOffset += 4;
215
- if (byteOffset + partLen > source.byteLength) {
216
- throw new Error('Fetch request header value was truncated.');
217
- }
218
- values.push(partLen > 0 ? decoder.decode(source.subarray(byteOffset, byteOffset + partLen)) : '');
219
- byteOffset += partLen;
220
- }
221
- return values;
222
- }
223
-
224
- writeAppFloat32(ptr: number, value: number): void {
225
- const appView = new DataView(this.getCurrentMemory().buffer);
226
- appView.setFloat32(ptr, value, true);
227
- }
228
-
229
- writeAppUint32(ptr: number, value: number): void {
230
- const appView = new DataView(this.getCurrentMemory().buffer);
231
- appView.setUint32(ptr, value >>> 0, true);
232
- }
233
-
234
- writeAppUtf8(ptr: number, capacity: number, text: string, context: string): number {
235
- if (capacity <= 0) {
236
- if (text.length === 0) {
237
- return 0;
238
- }
239
- throw new Error(`${context} cannot write into a zero-length host-service buffer.`);
240
- }
241
- const encoded = encoder.encode(text);
242
- if (encoded.length > capacity) {
243
- throw new Error(`${context} exceeds the provided host-service buffer.`);
244
- }
245
- if (encoded.length > 0) {
246
- const memory = new Uint8Array(this.getCurrentMemory().buffer, ptr, encoded.length);
247
- memory.set(encoded);
248
- }
249
- return encoded.length;
250
- }
251
-
252
- writeTextCallbackPayload(session: TextSessionLike, text: string, context: string): number {
253
- const encoded = encoder.encode(text);
254
- if (encoded.length > session.textBufferSize) {
255
- throw new Error(`${context} exceeds the shared AssemblyScript text buffer.`);
256
- }
257
- if (encoded.length > 0) {
258
- const memory = new Uint8Array(session.memory.buffer, session.textBufferPtr, encoded.length);
259
- memory.set(encoded);
260
- }
261
- return encoded.length;
262
- }
263
-
264
- writeWorkerTextCallbackPayload(session: TextSessionLike, text: string, context: string): number {
265
- return this.writeTextCallbackPayload(session, text, context);
266
- }
267
-
268
- writeTextToSessionBuffer(session: TextSessionLike, text: string): number {
269
- if (session.textBufferPtr === 0 || session.textBufferSize === 0) {
270
- return 0;
271
- }
272
- const encoded = encoder.encode(text);
273
- const length = Math.min(encoded.length, session.textBufferSize);
274
- if (length > 0) {
275
- const memory = new Uint8Array(session.memory.buffer, session.textBufferPtr, length);
276
- memory.set(encoded.subarray(0, length));
277
- }
278
- return length;
279
- }
280
-
281
- withUiUtf8(
282
- text: string,
283
- callback: (ptr: WasmHandleLike | number, len: number) => void,
284
- ): void {
285
- const runtime = this.getRuntime();
286
- if (text.length === 0) {
287
- callback(zeroPointer(runtime), 0);
288
- return;
289
- }
290
- const bytes = encoder.encode(text);
291
- const ptr = runtime.ui._malloc(bytes.length);
292
- const numericPtr = toNumberHandle(ptr);
293
- runtime.ui.HEAPU8.set(bytes, numericPtr);
294
- callback(normalizePointer(runtime, ptr), bytes.length);
295
- runtime.ui._free(ptr);
296
- }
297
-
298
- withUiGridData(
299
- values: Float32Array,
300
- types: Uint8Array,
301
- callback: (valuesPtr: WasmHandleLike | number, typesPtr: WasmHandleLike | number) => void,
302
- ): void {
303
- const runtime = this.getRuntime();
304
- const valueBytes = new Uint8Array(values.buffer);
305
- const valuePtr = valueBytes.length > 0 ? runtime.ui._malloc(valueBytes.length) : zeroPointer(runtime);
306
- const valueNumericPtr = valueBytes.length > 0 ? toNumberHandle(valuePtr) : 0;
307
- if (valueBytes.length > 0) {
308
- runtime.ui.HEAPU8.set(valueBytes, valueNumericPtr);
309
- }
310
-
311
- const typePtr = types.length > 0 ? runtime.ui._malloc(types.length) : zeroPointer(runtime);
312
- const typeNumericPtr = types.length > 0 ? toNumberHandle(typePtr) : 0;
313
- if (types.length > 0) {
314
- runtime.ui.HEAPU8.set(types, typeNumericPtr);
315
- }
316
-
317
- callback(normalizePointer(runtime, valuePtr), normalizePointer(runtime, typePtr));
318
-
319
- if (types.length > 0) {
320
- runtime.ui._free(typePtr);
321
- }
322
- if (valueBytes.length > 0) {
323
- runtime.ui._free(valuePtr);
324
- }
325
- }
326
-
327
- withUiGradientData(
328
- offsets: Float32Array,
329
- colors: Uint32Array,
330
- callback: (offsetsPtr: WasmHandleLike | number, colorsPtr: WasmHandleLike | number) => void,
331
- ): void {
332
- const runtime = this.getRuntime();
333
- const offsetBytes = new Uint8Array(offsets.buffer);
334
- const offsetPtr = offsetBytes.length > 0 ? runtime.ui._malloc(offsetBytes.length) : zeroPointer(runtime);
335
- const offsetNumericPtr = offsetBytes.length > 0 ? toNumberHandle(offsetPtr) : 0;
336
- if (offsetBytes.length > 0) {
337
- runtime.ui.HEAPU8.set(offsetBytes, offsetNumericPtr);
338
- }
339
-
340
- const colorBytes = new Uint8Array(colors.buffer);
341
- const colorPtr = colorBytes.length > 0 ? runtime.ui._malloc(colorBytes.length) : zeroPointer(runtime);
342
- const colorNumericPtr = colorBytes.length > 0 ? toNumberHandle(colorPtr) : 0;
343
- if (colorBytes.length > 0) {
344
- runtime.ui.HEAPU8.set(colorBytes, colorNumericPtr);
345
- }
346
-
347
- callback(normalizePointer(runtime, offsetPtr), normalizePointer(runtime, colorPtr));
348
-
349
- if (colorBytes.length > 0) {
350
- runtime.ui._free(colorPtr);
351
- }
352
- if (offsetBytes.length > 0) {
353
- runtime.ui._free(offsetPtr);
354
- }
355
- }
356
-
357
- recordTextChanged(handle: AppHandleLike, text: string): void {
358
- this.latestTextByHandle.set(toBigIntHandle(handle).toString(), text);
359
- }
360
-
361
- recordTextReplaced(handle: AppHandleLike, start: number, end: number, text: string): void {
362
- const handleKey = toBigIntHandle(handle).toString();
363
- const previousText = this.latestTextByHandle.get(handleKey) ?? '';
364
- this.latestTextByHandle.set(handleKey, applyReplacementEdit(previousText, start, end, text));
365
- }
366
-
367
- recordSelectionChanged(handle: AppHandleLike, start: number, end: number): void {
368
- this.latestSelectionByHandle.set(toBigIntHandle(handle).toString(), { start, end });
369
- }
370
-
371
- getLatestText(handle: AppHandleLike): string {
372
- return this.latestTextByHandle.get(toBigIntHandle(handle).toString()) ?? '';
373
- }
374
-
375
- resolveFrozenOrLiveTextSelection(handle: AppHandleLike): FrozenTextSelectionSnapshot | null {
376
- const handleKey = toBigIntHandle(handle).toString();
377
- if (
378
- this.frozenTextSelectionSnapshot !== null &&
379
- this.frozenTextSelectionSnapshot.handleKey === handleKey &&
380
- this.frozenTextSelectionSnapshot.start !== this.frozenTextSelectionSnapshot.end
381
- ) {
382
- return this.frozenTextSelectionSnapshot;
383
- }
384
- const text = this.latestTextByHandle.get(handleKey) ?? '';
385
- const selection = this.latestSelectionByHandle.get(handleKey) ?? null;
386
- if (selection === null || selection.start === selection.end || text.length === 0) {
387
- return null;
388
- }
389
- const start = Math.max(0, Math.min(selection.start, selection.end));
390
- const end = Math.max(start, Math.min(text.length, Math.max(selection.start, selection.end)));
391
- return { handleKey, text, start, end };
392
- }
393
-
394
- freezeTextSelectionSnapshot(handle: AppHandleLike): void {
395
- this.frozenTextSelectionSnapshot = this.resolveFrozenOrLiveTextSelection(handle);
396
- }
397
-
398
- clearFrozenTextSelectionSnapshot(): void {
399
- this.frozenTextSelectionSnapshot = null;
400
- }
401
-
402
- getHiddenTextEditor(): HTMLInputElement | HTMLTextAreaElement | null {
403
- return getHiddenTextEditor();
404
- }
405
-
406
- syncEditableTextToRuntime(handle: AppHandleLike, text: string, caret: number): void {
407
- const runtime = this.getRuntime();
408
- const handleKey = toBigIntHandle(handle).toString();
409
- const previousText = this.latestTextByHandle.get(handleKey) ?? '';
410
- const replacement = computeReplacementEdit(previousText, text);
411
- if (replacement === null) {
412
- runtime.commitFrame();
413
- this.queueHarnessFrame();
414
- return;
415
- }
416
- const intendedText =
417
- `${previousText.slice(0, replacement.start)}${replacement.insertedText}${previousText.slice(replacement.end)}`;
418
- const intendedCaret = Math.max(0, Math.min(caret, intendedText.length));
419
- const clamped = clampTextboxHardLines(intendedText);
420
- const committedText = clamped.text;
421
- const clampedCaret = clamped.mapIndex(intendedCaret);
422
- const editor = getHiddenTextEditor();
423
- if (editor !== null && editor.value !== committedText) {
424
- editor.value = committedText;
425
- editor.setSelectionRange(clampedCaret, clampedCaret, 'none');
426
- }
427
- const committedReplacement = computeReplacementEdit(previousText, committedText);
428
- if (committedReplacement === null) {
429
- runtime.commitFrame();
430
- this.queueHarnessFrame();
431
- return;
432
- }
433
- runtime.ui._ui_set_interaction_time(currentInteractionTimeMs());
434
- this.withUiUtf8(committedReplacement.insertedText, (uiPtr, uiLen) => {
435
- runtime.ui._ui_replace_text_range(
436
- toBigIntHandle(handle),
437
- committedReplacement.start,
438
- committedReplacement.end,
439
- uiPtr,
440
- uiLen,
441
- clampedCaret,
442
- );
443
- });
444
- runtime.commitFrame();
445
- this.queueHarnessFrame();
446
- }
447
-
448
- updateLiveTextAfterCut(handleKey: string, text: string, caret: number): void {
449
- this.latestTextByHandle.set(handleKey, text);
450
- this.latestSelectionByHandle.set(handleKey, { start: caret, end: caret });
451
- }
452
- }
@@ -1,206 +0,0 @@
1
- import type { BridgeRuntime, EffinDomRuntimeConfig, WasmHandleLike } from '@effindomv2/runtime';
2
-
3
- import type { HostEventsDefinition } from '../host-events';
4
- import type { HostServicesDefinition } from '../host-services';
5
- import type { WorkerHostServicesBundleConfig } from '../worker-types';
6
-
7
- export interface HarnessState {
8
- readonly commandWordCount: number;
9
- readonly commandWords: readonly number[];
10
- readonly rootHandle: string | null;
11
- }
12
-
13
- export interface HarnessExports {
14
- readonly memory: WebAssembly.Memory;
15
- __flushRenders(): void;
16
- __fui_capture_persisted_ui_state?(): void;
17
- __fui_debug_pointer_event?(eventType: number, handle: bigint, x: number, y: number, modifiers: number): void;
18
- __fui_debug_focus_changed?(handle: bigint, focused: boolean): void;
19
- __fui_debug_key_event?(eventType: number, keyPtr: number, keyLen: number, modifiers: number): void;
20
- __fui_debug_scroll?(
21
- handle: bigint,
22
- offsetX: number,
23
- offsetY: number,
24
- contentWidth: number,
25
- contentHeight: number,
26
- viewportWidth: number,
27
- viewportHeight: number,
28
- ): void;
29
- __fui_on_pointer_event(eventType: number, handle: bigint, x: number, y: number, modifiers: number): void;
30
- __fui_on_external_drag_event(
31
- eventType: number,
32
- handle: bigint,
33
- x: number,
34
- y: number,
35
- modifiers: number,
36
- payloadPtr: number,
37
- payloadLen: number,
38
- ): number;
39
- __fui_on_fetch_complete(
40
- requestId: number,
41
- ok: boolean,
42
- status: number,
43
- payloadPtr: number,
44
- payloadLen: number,
45
- ): void;
46
- __fui_on_fetch_error(requestId: number, payloadPtr: number, payloadLen: number): void;
47
- __fui_on_file_pick_result(requestId: number, status: number, payloadPtr: number, payloadLen: number): void;
48
- __fui_on_file_read_result(
49
- requestId: number,
50
- status: number,
51
- offsetBytes: bigint,
52
- fileSizeBytes: bigint,
53
- payloadPtr: number,
54
- payloadLen: number,
55
- ): void;
56
- __fui_on_file_save_result(
57
- requestId: number,
58
- status: number,
59
- writtenBytes: bigint,
60
- payloadPtr: number,
61
- payloadLen: number,
62
- ): void;
63
- __fui_on_file_writer_created(requestId: number, status: number, payloadPtr: number, payloadLen: number): void;
64
- __fui_on_file_write_result(
65
- requestId: number,
66
- status: number,
67
- writtenBytes: bigint,
68
- totalWrittenBytes: bigint,
69
- payloadPtr: number,
70
- payloadLen: number,
71
- ): void;
72
- __fui_on_file_finish_result(
73
- requestId: number,
74
- status: number,
75
- writtenBytes: bigint,
76
- payloadPtr: number,
77
- payloadLen: number,
78
- ): void;
79
- __fui_on_file_worker_process_progress(
80
- requestId: number,
81
- copiedBytes: bigint,
82
- totalBytes: bigint,
83
- payloadPtr: number,
84
- payloadLen: number,
85
- ): void;
86
- __fui_on_file_worker_process_chunk(
87
- requestId: number,
88
- offsetBytes: bigint,
89
- fileSizeBytes: bigint,
90
- payloadPtr: number,
91
- payloadLen: number,
92
- ): void;
93
- __fui_on_file_worker_process_complete(
94
- requestId: number,
95
- writtenBytes: bigint,
96
- payloadPtr: number,
97
- payloadLen: number,
98
- ): void;
99
- __fui_on_file_worker_process_error(requestId: number, status: number, payloadPtr: number, payloadLen: number): void;
100
- __fui_on_context_menu(handle: bigint, x: number, y: number): void;
101
- __fui_hide_active_context_menu(): void;
102
- __fui_key_buffer(): number;
103
- __fui_text_buffer(): number;
104
- __fui_text_buffer_size(): number;
105
- __fui_on_focus_changed(handle: bigint, focused: boolean): void;
106
- __fui_on_text_changed(handle: bigint, textPtr: number, textLen: number): void;
107
- __fui_on_text_replaced(handle: bigint, start: number, end: number, textPtr: number, textLen: number): void;
108
- __fui_on_selection_changed(handle: bigint, start: number, end: number): void;
109
- __fui_on_key_event(eventType: number, keyPtr: number, keyLen: number, modifiers: number): number;
110
- __fui_on_scroll(
111
- handle: bigint,
112
- offsetX: number,
113
- offsetY: number,
114
- contentWidth: number,
115
- contentHeight: number,
116
- viewportWidth: number,
117
- viewportHeight: number,
118
- ): void;
119
- __fui_on_cross_selection_changed(handle: bigint, textPtr: number, textLen: number): void;
120
- __fui_on_route_changed(routePtr: number, routeLen: number): void;
121
- __fui_on_viewport_changed(width: number, height: number): void;
122
- __fui_on_system_dark_mode_changed(isDark: boolean): void;
123
- __fui_on_svg_loaded(svgId: number, width: number, height: number): void;
124
- __fui_on_svg_failed(svgId: number, errorPtr: number, errorLen: number): void;
125
- __fui_on_texture_loaded(textureId: number, width: number, height: number): void;
126
- __fui_on_texture_failed(textureId: number, errorPtr: number, errorLen: number): void;
127
- __fui_on_frame(timestampMs: number): void;
128
- __fui_on_timer(timerId: number): void;
129
- __fui_on_worker_progress(workerId: number, textPtr: number, textLen: number): void;
130
- __fui_on_worker_complete(workerId: number, textPtr: number, textLen: number): void;
131
- __fui_on_worker_error(workerId: number, textPtr: number, textLen: number): void;
132
- __fui_restore_persisted_ui_state?(): void;
133
- fui_dispatch_custom_draw?(handle: bigint, canvasPtr: number): void;
134
- }
135
-
136
- export interface HarnessContext<Exports extends HarnessExports> {
137
- readonly runtime: BridgeRuntime;
138
- readonly exports: Exports;
139
- waitForFrame(): Promise<void>;
140
- }
141
-
142
- export interface HarnessOptions<Exports extends HarnessExports> {
143
- wasmPath: string;
144
- run?(exports: Exports): void;
145
- onStateUpdated?(state: HarnessState): void;
146
- onReady?(context: HarnessContext<Exports>): void | Promise<void>;
147
- onDispose?(exports: Exports): void;
148
- onError?(error: unknown): void;
149
- showLoadingOverlay?: boolean;
150
- hostEvents?: HostEventsDefinition;
151
- hostServices?: HostServicesDefinition;
152
- workerHostServices?: WorkerHostServicesBundleConfig;
153
- persistedRestoreMode?: 'initial' | 'pop' | 'none';
154
- }
155
-
156
- export interface HarnessAppOptions<Exports extends HarnessExports> extends HarnessOptions<Exports> {
157
- run(exports: Exports): void;
158
- }
159
-
160
- export type HarnessNavigationMode = 'push' | 'replace' | 'pop';
161
-
162
- export interface HarnessController {
163
- readonly runtime: BridgeRuntime;
164
- waitForFrame(): Promise<void>;
165
- loadApp<Exports extends HarnessExports>(options: HarnessAppOptions<Exports>): Promise<HarnessContext<Exports>>;
166
- unloadApp(): Promise<void>;
167
- recreateRuntime(): Promise<BridgeRuntime>;
168
- setSameOriginNavigationHandler(
169
- handler: ((target: URL, mode: HarnessNavigationMode) => void | Promise<void>) | null,
170
- ): void;
171
- }
172
-
173
- export interface ManagedHarnessOptions {
174
- onReady?(controller: HarnessController): void | Promise<void>;
175
- onError?(error: unknown): void;
176
- }
177
-
178
- export interface ManagedHistoryState {
179
- readonly href: string;
180
- readonly uiSnapshotId?: string;
181
- }
182
-
183
- export interface HarnessDebugApi {
184
- flush(): Promise<void>;
185
- pointerEvent(type: number, handle: WasmHandleLike, x: number, y: number, modifiers?: number): Promise<void>;
186
- focusChanged(handle: WasmHandleLike, focused: boolean): Promise<void>;
187
- keyEvent(type: number, key: string, modifiers?: number): Promise<void>;
188
- navigateTo(target: string): Promise<void>;
189
- scroll(
190
- handle: WasmHandleLike,
191
- offsetX: number,
192
- offsetY: number,
193
- contentWidth: number,
194
- contentHeight: number,
195
- viewportWidth: number,
196
- viewportHeight: number,
197
- ): Promise<void>;
198
- }
199
-
200
- declare global {
201
- interface Window {
202
- __effindomRuntime?: EffinDomRuntimeConfig;
203
- __fui_debug?: HarnessDebugApi;
204
- __fuiUrlPreviewText?: string;
205
- }
206
- }