@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,14 +1,15 @@
1
1
  import * as ui from "../../bindings/ui";
2
2
  import { HandlerAction } from "../../core/Action";
3
- import { Callback1, Callback2, Handler1, Handler2 } from "../../core/BoundCallback";
3
+ import { Callback1, Handler1 } from "../../core/BoundCallback";
4
4
  import { Disposable, disposeAll } from "../../core/Disposable";
5
5
  import { FocusAdornerManager } from "../../core/FocusAdornerManager";
6
6
  import { keyboardFocusVisible } from "../../core/FocusVisibility";
7
- import { Node } from "../../core/Node";
7
+ import { FocusChangedEventArgs, Node, SelectionChangedEventArgs, TextChangedEventArgs } from "../../core/Node";
8
8
  import {
9
- BorderStyle,
10
9
  CursorStyle,
11
10
  HandleValue,
11
+ KeyEventType,
12
+ KeyModifier,
12
13
  PointerEventType,
13
14
  SemanticRole,
14
15
  TextVerticalAlign,
@@ -18,7 +19,7 @@ import { Theme, activeTheme } from "../../core/Theme";
18
19
  import { FontFamily } from "../../core/Typography";
19
20
  import { PersistedStringCodec, PersistedValueState } from "../../core/PersistedState";
20
21
  import { FlexBox, ScrollBarVisibility, ScrollBox, TextCore } from "../../nodes";
21
- import { bind1, bind2 } from "../../core/bind";
22
+ import { bind1 } from "../../core/bind";
22
23
  import { ScrollState } from "../../nodes/ScrollState";
23
24
  import { ScrollView } from "../../nodes/ScrollView";
24
25
  import { getControlTemplates } from "../ControlTemplateSet";
@@ -42,6 +43,93 @@ function utf8ByteLength(text: string): u32 {
42
43
  return <u32>bytes.length;
43
44
  }
44
45
 
46
+ function utf8CodePointByteLength(codePoint: u32): u32 {
47
+ if (codePoint <= 0x7F) {
48
+ return 1;
49
+ }
50
+ if (codePoint <= 0x7FF) {
51
+ return 2;
52
+ }
53
+ if (codePoint <= 0xFFFF) {
54
+ return 3;
55
+ }
56
+ return 4;
57
+ }
58
+
59
+ function readCodePoint(text: string, index: i32): u32 {
60
+ const first = <u32>text.charCodeAt(index);
61
+ if (first < 0xD800 || first > 0xDBFF || index + 1 >= text.length) {
62
+ return first;
63
+ }
64
+ const second = <u32>text.charCodeAt(index + 1);
65
+ if (second < 0xDC00 || second > 0xDFFF) {
66
+ return first;
67
+ }
68
+ return 0x10000 + ((first - 0xD800) << 10) + (second - 0xDC00);
69
+ }
70
+
71
+ function codePointStringLength(codePoint: u32): i32 {
72
+ return codePoint > 0xFFFF ? 2 : 1;
73
+ }
74
+
75
+ function codePointCount(text: string): u32 {
76
+ let count: u32 = 0;
77
+ let cursor = 0;
78
+ while (cursor < text.length) {
79
+ const codePoint = readCodePoint(text, cursor);
80
+ cursor += codePointStringLength(codePoint);
81
+ count += 1;
82
+ }
83
+ return count;
84
+ }
85
+
86
+ function utf8ByteOffsetFromCodePointIndex(text: string, index: u32): u32 {
87
+ const countLimit = codePointCount(text);
88
+ const clampedIndex = index > countLimit ? countLimit : index;
89
+ let byteOffset: u32 = 0;
90
+ let cursor = 0;
91
+ let count: u32 = 0;
92
+ while (cursor < text.length && count < clampedIndex) {
93
+ const codePoint = readCodePoint(text, cursor);
94
+ byteOffset += utf8CodePointByteLength(codePoint);
95
+ cursor += codePointStringLength(codePoint);
96
+ count += 1;
97
+ }
98
+ return byteOffset;
99
+ }
100
+
101
+ function stringOffsetFromCodePointIndex(text: string, index: u32): i32 {
102
+ const countLimit = codePointCount(text);
103
+ const clampedIndex = index > countLimit ? countLimit : index;
104
+ let cursor = 0;
105
+ let count: u32 = 0;
106
+ while (cursor < text.length && count < clampedIndex) {
107
+ const codePoint = readCodePoint(text, cursor);
108
+ cursor += codePointStringLength(codePoint);
109
+ count += 1;
110
+ }
111
+ return cursor;
112
+ }
113
+
114
+ function codePointIndexFromUtf8ByteOffset(text: string, byteOffset: u32): u32 {
115
+ const length = utf8ByteLength(text);
116
+ const clampedOffset = byteOffset > length ? length : byteOffset;
117
+ let bytes: u32 = 0;
118
+ let cursor = 0;
119
+ let count: u32 = 0;
120
+ while (cursor < text.length) {
121
+ const codePoint = readCodePoint(text, cursor);
122
+ const byteLength = utf8CodePointByteLength(codePoint);
123
+ if (bytes + byteLength > clampedOffset) {
124
+ break;
125
+ }
126
+ bytes += byteLength;
127
+ cursor += codePointStringLength(codePoint);
128
+ count += 1;
129
+ }
130
+ return count;
131
+ }
132
+
45
133
  export class TextInputProfile {
46
134
  constructor(
47
135
  readonly maxLines: i32,
@@ -109,6 +197,16 @@ class TextInputEditorText extends TextCore {
109
197
  super._handleSelectionChanged(start, end);
110
198
  this.owner.handleEditorSelectionChanged(start, end);
111
199
  }
200
+
201
+ _handleKeyEvent(eventType: KeyEventType, key: string, modifiers: u32): bool {
202
+ if (super._handleKeyEvent(eventType, key, modifiers)) {
203
+ return true;
204
+ }
205
+ if (eventType == KeyEventType.Down && this.owner.handleEditorKeyDown(key, modifiers)) {
206
+ return true;
207
+ }
208
+ return false;
209
+ }
112
210
  }
113
211
 
114
212
  class TextInputPlaceholderHost extends FlexBox {
@@ -163,9 +261,13 @@ export class TextInputCore extends FlexBox {
163
261
  private placeholderValue: string = "";
164
262
  private selectionStartValue: u32 = 0;
165
263
  private selectionEndValue: u32 = 0;
264
+ private selectionStartByteValue: u32 = 0;
265
+ private selectionEndByteValue: u32 = 0;
166
266
  private focusedState: bool = false;
167
267
  private readOnlyValue: bool = false;
168
268
  private passwordValue: bool = false;
269
+ private acceptsTabValue: bool = false;
270
+ private hostAutofillHintValue: string | null = null;
169
271
  private maxCharsValue: i32 = UNLIMITED_TEXT_LENGTH;
170
272
  private wrappingValue: bool;
171
273
  private verticalScrollbarVisibilityValue: ScrollBarVisibility = ScrollBarVisibility.Auto;
@@ -173,12 +275,12 @@ export class TextInputCore extends FlexBox {
173
275
  private fontFamilyOverride: FontFamily | null = null;
174
276
  private fontSizeOverride: f32 = 0.0;
175
277
  private hasFontSizeOverride: bool = false;
176
- private changedCb: ((text: string) => void) | null = null;
177
- private changedBinding: Callback1<string> | null = null;
178
- private selectionChangedCb: ((start: u32, end: u32) => void) | null = null;
179
- private selectionChangedBinding: Callback2<u32, u32> | null = null;
180
- private focusChangedListener: ((focused: bool) => void) | null = null;
181
- private textInputFocusChangedBinding: Callback1<bool> | null = null;
278
+ private changedCb: ((event: TextChangedEventArgs) => void) | null = null;
279
+ private changedBinding: Callback1<TextChangedEventArgs> | null = null;
280
+ private selectionChangedCb: ((event: SelectionChangedEventArgs) => void) | null = null;
281
+ private selectionChangedBinding: Callback1<SelectionChangedEventArgs> | null = null;
282
+ private focusChangedListener: ((event: FocusChangedEventArgs) => void) | null = null;
283
+ private textInputFocusChangedBinding: Callback1<FocusChangedEventArgs> | null = null;
182
284
  private templateValue: TextInputTemplate | null = null;
183
285
  private colorsValue: TextInputColors | null = null;
184
286
 
@@ -186,6 +288,12 @@ export class TextInputCore extends FlexBox {
186
288
  super();
187
289
  this.profile = profile;
188
290
  this.textValue = text;
291
+ const initialCaret = codePointCount(text);
292
+ const initialCaretByte = utf8ByteLength(text);
293
+ this.selectionStartValue = initialCaret;
294
+ this.selectionEndValue = initialCaret;
295
+ this.selectionStartByteValue = initialCaretByte;
296
+ this.selectionEndByteValue = initialCaretByte;
189
297
  this.wrappingValue = profile.wrapsByDefault;
190
298
  this.editorText = new TextInputEditorText(this, text);
191
299
  this.placeholderText = new TextCore("");
@@ -213,6 +321,7 @@ export class TextInputCore extends FlexBox {
213
321
  this.syncSemanticLabel();
214
322
  this.syncEditorLimits();
215
323
  this.syncEditorEditability();
324
+ this.syncBrowserInputMetadata();
216
325
  this.syncThemeState(activeTheme.value);
217
326
  this.syncPlaceholderVisibility();
218
327
  this.persistState(TEXT_INPUT_PERSISTED_STATE);
@@ -234,6 +343,14 @@ export class TextInputCore extends FlexBox {
234
343
  return this.selectionEndValue;
235
344
  }
236
345
 
346
+ get selectionStartByteOffset(): u32 {
347
+ return this.selectionStartByteValue;
348
+ }
349
+
350
+ get selectionEndByteOffset(): u32 {
351
+ return this.selectionEndByteValue;
352
+ }
353
+
237
354
  get isFocused(): bool {
238
355
  return this.focusedState;
239
356
  }
@@ -266,13 +383,43 @@ export class TextInputCore extends FlexBox {
266
383
 
267
384
  text(value: string): this {
268
385
  this.textValue = value;
269
- this.clampSelectionToText();
270
386
  this.editorText.text(value);
387
+ this.caretStringIndex(codePointCount(value));
271
388
  this.syncSemanticLabel();
272
389
  this.syncPlaceholderVisibility();
273
390
  return this;
274
391
  }
275
392
 
393
+ selectionRange(start: u32, end: u32): this {
394
+ const length = codePointCount(this.textValue);
395
+ const clampedStart = clampSelection(start, <i32>length);
396
+ const clampedEnd = clampSelection(end, <i32>length);
397
+ const clampedStartByte = utf8ByteOffsetFromCodePointIndex(this.textValue, clampedStart);
398
+ const clampedEndByte = utf8ByteOffsetFromCodePointIndex(this.textValue, clampedEnd);
399
+ this.selectionStartValue = clampedStart;
400
+ this.selectionEndValue = clampedEnd;
401
+ this.selectionStartByteValue = clampedStartByte;
402
+ this.selectionEndByteValue = clampedEndByte;
403
+ const handle = this.editorText.builtHandle;
404
+ if (handle != <u64>HandleValue.Invalid) {
405
+ ui.setTextSelectionRange(handle, clampedStartByte, clampedEndByte);
406
+ }
407
+ return this;
408
+ }
409
+
410
+ caret(position: u32): this {
411
+ return this.caretStringIndex(position);
412
+ }
413
+
414
+ caretToEnd(): this {
415
+ const end = codePointCount(this.textValue);
416
+ return this.caretStringIndex(end);
417
+ }
418
+
419
+ private caretStringIndex(position: u32): this {
420
+ return this.selectionRange(position, position);
421
+ }
422
+
276
423
  placeholder(value: string): this {
277
424
  this.placeholderValue = value;
278
425
  this.placeholderText.text(value);
@@ -293,65 +440,78 @@ export class TextInputCore extends FlexBox {
293
440
  return this;
294
441
  }
295
442
 
443
+ acceptsTab(flag: bool = true): this {
444
+ this.acceptsTabValue = flag;
445
+ this.editorText._editorAcceptsTab(flag);
446
+ return this;
447
+ }
448
+
296
449
  password(flag: bool = true): this {
297
450
  this.passwordValue = flag;
298
451
  this.editorText.obscured(flag);
452
+ this.syncBrowserInputMetadata();
299
453
  this.syncSemanticLabel();
300
454
  return this;
301
455
  }
302
456
 
303
- onChanged(cb: (text: string) => void): this {
457
+ hostAutofill(hint: string | null = null): this {
458
+ this.hostAutofillHintValue = hint !== null && hint.length > 0 ? hint : null;
459
+ this.syncBrowserInputMetadata();
460
+ return this;
461
+ }
462
+
463
+ onChanged(cb: (event: TextChangedEventArgs) => void): this {
304
464
  this.changedCb = cb;
305
465
  this.changedBinding = null;
306
466
  return this;
307
467
  }
308
468
 
309
- bindChanged<Owner>(owner: Owner, handler: Handler1<Owner, string>): this {
469
+ bindChanged<Owner>(owner: Owner, handler: Handler1<Owner, TextChangedEventArgs>): this {
310
470
  this.changedCb = null;
311
- this.changedBinding = bind1<Owner, string>(owner, handler);
471
+ this.changedBinding = bind1<Owner, TextChangedEventArgs>(owner, handler);
312
472
  return this;
313
473
  }
314
474
 
315
- onChangedWith<Owner>(owner: Owner, handler: Handler1<Owner, string>): this {
475
+ onChangedWith<Owner>(owner: Owner, handler: Handler1<Owner, TextChangedEventArgs>): this {
316
476
  this.bindChanged(owner, handler);
317
477
  return this;
318
478
  }
319
479
 
320
- onTextChanged(cb: (text: string) => void): this {
480
+ onTextChanged(cb: (event: TextChangedEventArgs) => void): this {
321
481
  this.onChanged(cb);
322
482
  return this;
323
483
  }
324
484
 
325
- onSelectionChanged(cb: (start: u32, end: u32) => void): this {
485
+ onSelectionChanged(cb: (event: SelectionChangedEventArgs) => void): this {
326
486
  this.selectionChangedCb = cb;
327
487
  this.selectionChangedBinding = null;
328
488
  return this;
329
489
  }
330
490
 
331
- bindSelectionChanged<Owner>(owner: Owner, handler: Handler2<Owner, u32, u32>): this {
491
+ bindSelectionChanged<Owner>(owner: Owner, handler: Handler1<Owner, SelectionChangedEventArgs>): this {
332
492
  this.selectionChangedCb = null;
333
- this.selectionChangedBinding = bind2<Owner, u32, u32>(owner, handler);
493
+ this.selectionChangedBinding = bind1<Owner, SelectionChangedEventArgs>(owner, handler);
334
494
  return this;
335
495
  }
336
496
 
337
- onSelectionChangedWith<Owner>(owner: Owner, handler: Handler2<Owner, u32, u32>): this {
497
+ onSelectionChangedWith<Owner>(owner: Owner, handler: Handler1<Owner, SelectionChangedEventArgs>): this {
338
498
  this.bindSelectionChanged(owner, handler);
339
499
  return this;
340
500
  }
341
501
 
342
- onFocusChanged(cb: (focused: bool) => void): this {
502
+ onFocusChanged(cb: (event: FocusChangedEventArgs) => void): this {
343
503
  this.focusChangedListener = cb;
344
504
  this.textInputFocusChangedBinding = null;
345
505
  return this;
346
506
  }
347
507
 
348
- bindFocusChanged<Owner>(owner: Owner, handler: Handler1<Owner, bool>): this {
508
+ bindFocusChanged<Owner>(owner: Owner, handler: Handler1<Owner, FocusChangedEventArgs>): this {
349
509
  this.focusChangedListener = null;
350
- this.textInputFocusChangedBinding = bind1<Owner, bool>(owner, handler);
510
+ this.textInputFocusChangedBinding = bind1<Owner, FocusChangedEventArgs>(owner, handler);
351
511
  return this;
352
512
  }
353
513
 
354
- onFocusChangedWith<Owner>(owner: Owner, handler: Handler1<Owner, bool>): this {
514
+ onFocusChangedWith<Owner>(owner: Owner, handler: Handler1<Owner, FocusChangedEventArgs>): this {
355
515
  this.bindFocusChanged(owner, handler);
356
516
  return this;
357
517
  }
@@ -382,6 +542,7 @@ export class TextInputCore extends FlexBox {
382
542
 
383
543
  focusNow(): this {
384
544
  this.focusEditor();
545
+ this.selectionByteRange(this.selectionStartByteValue, this.selectionEndByteValue);
385
546
  return this;
386
547
  }
387
548
 
@@ -420,8 +581,12 @@ export class TextInputCore extends FlexBox {
420
581
  if (this.builtHandle != <u64>HandleValue.Invalid) {
421
582
  return this.builtHandle;
422
583
  }
584
+ const intendedSelectionStartByte = this.selectionStartByteValue;
585
+ const intendedSelectionEndByte = this.selectionEndByteValue;
423
586
  const handle = super.build();
424
587
  ui.setSelectionAreaBarrier(handle, true);
588
+ this.syncBrowserInputMetadata();
589
+ this.selectionByteRange(intendedSelectionStartByte, intendedSelectionEndByte);
425
590
  return handle;
426
591
  }
427
592
 
@@ -524,7 +689,7 @@ export class TextInputCore extends FlexBox {
524
689
  this.bgColor(colors.backgroundColor);
525
690
  }
526
691
  if (colors !== null && colors.hasBorder) {
527
- this.border(1.0, colors.borderColor, BorderStyle.Solid);
692
+ this.border(1.0, colors.borderColor);
528
693
  }
529
694
 
530
695
  this.syncFocusChrome(theme);
@@ -537,17 +702,21 @@ export class TextInputCore extends FlexBox {
537
702
  this.focusedState = focused;
538
703
  this.syncFocusChrome(activeTheme.value);
539
704
  this.syncPlaceholderVisibility();
705
+ const event = new FocusChangedEventArgs(focused);
540
706
  const callback = this.focusChangedListener;
541
707
  if (callback !== null) {
542
- callback(focused);
708
+ callback(event);
543
709
  }
544
710
  const binding = this.textInputFocusChangedBinding;
545
711
  if (binding !== null) {
546
- binding.invoke(focused);
712
+ binding.invoke(event);
547
713
  }
548
714
  }
549
715
 
550
716
  handleEditorTextChanged(value: string): void {
717
+ if (this.textValue == value) {
718
+ return;
719
+ }
551
720
  this.textValue = value;
552
721
  this.clampSelectionToText();
553
722
  this.syncSemanticLabel();
@@ -555,27 +724,97 @@ export class TextInputCore extends FlexBox {
555
724
  this.emitTextChanged(value);
556
725
  }
557
726
 
727
+ handleEditorKeyDown(key: string, modifiers: u32): bool {
728
+ if (!this.isEnabled) {
729
+ return false;
730
+ }
731
+ const nonShiftModifiers = modifiers & (<u32>KeyModifier.Ctrl | <u32>KeyModifier.Alt | <u32>KeyModifier.Meta);
732
+ if (
733
+ nonShiftModifiers == 0 &&
734
+ (
735
+ key == "ArrowLeft" ||
736
+ key == "ArrowRight" ||
737
+ key == "ArrowUp" ||
738
+ key == "ArrowDown" ||
739
+ key == "Home" ||
740
+ key == "End" ||
741
+ key == "PageUp" ||
742
+ key == "PageDown"
743
+ )
744
+ ) {
745
+ return true;
746
+ }
747
+ if (this.acceptsTabValue && key == "Tab" && modifiers == 0 && !this.readOnlyValue) {
748
+ this.replaceSelectionWithText("\t");
749
+ return true;
750
+ }
751
+ return false;
752
+ }
753
+
754
+ private replaceSelectionWithText(inserted: string): void {
755
+ const text = this.textValue;
756
+ const start = this.selectionStartValue < this.selectionEndValue ? this.selectionStartValue : this.selectionEndValue;
757
+ const end = this.selectionStartValue > this.selectionEndValue ? this.selectionStartValue : this.selectionEndValue;
758
+ const currentLength = codePointCount(text);
759
+ const insertedLength = codePointCount(inserted);
760
+ const replacementLength = end - start;
761
+ if (currentLength - replacementLength + insertedLength > <u32>this.maxCharsValue) {
762
+ return;
763
+ }
764
+ const startOffset = stringOffsetFromCodePointIndex(text, start);
765
+ const endOffset = stringOffsetFromCodePointIndex(text, end);
766
+ const value = text.substring(0, startOffset) + inserted + text.substring(endOffset);
767
+ const caret = start + insertedLength;
768
+ this.textValue = value;
769
+ const handle = this.editorText.builtHandle;
770
+ if (handle != 0) {
771
+ const startByte = utf8ByteOffsetFromCodePointIndex(text, start);
772
+ const endByte = utf8ByteOffsetFromCodePointIndex(text, end);
773
+ const caretByte = utf8ByteOffsetFromCodePointIndex(value, caret);
774
+ ui.replaceTextRange(handle, startByte, endByte, inserted, caretByte);
775
+ this.selectionStartValue = caret;
776
+ this.selectionEndValue = caret;
777
+ this.selectionStartByteValue = caretByte;
778
+ this.selectionEndByteValue = caretByte;
779
+ } else {
780
+ this.editorText.text(value);
781
+ this.selectionRange(caret, caret);
782
+ }
783
+ this.syncSemanticLabel();
784
+ this.syncPlaceholderVisibility();
785
+ this.emitTextChanged(value);
786
+ this.emitSelectionChanged();
787
+ }
788
+
558
789
  private emitTextChanged(value: string): void {
790
+ const event = new TextChangedEventArgs(value);
559
791
  const callback = this.changedCb;
560
792
  if (callback !== null) {
561
- callback(value);
793
+ callback(event);
562
794
  }
563
795
  const binding = this.changedBinding;
564
796
  if (binding !== null) {
565
- binding.invoke(value);
797
+ binding.invoke(event);
566
798
  }
567
799
  }
568
800
 
569
801
  handleEditorSelectionChanged(start: u32, end: u32): void {
570
- this.selectionStartValue = start;
571
- this.selectionEndValue = end;
802
+ this.selectionStartByteValue = start;
803
+ this.selectionEndByteValue = end;
804
+ this.selectionStartValue = codePointIndexFromUtf8ByteOffset(this.textValue, start);
805
+ this.selectionEndValue = codePointIndexFromUtf8ByteOffset(this.textValue, end);
806
+ this.emitSelectionChanged();
807
+ }
808
+
809
+ private emitSelectionChanged(): void {
810
+ const event = new SelectionChangedEventArgs(this.selectionStartValue, this.selectionEndValue);
572
811
  const callback = this.selectionChangedCb;
573
812
  if (callback !== null) {
574
- callback(start, end);
813
+ callback(event);
575
814
  }
576
815
  const binding = this.selectionChangedBinding;
577
816
  if (binding !== null) {
578
- binding.invoke(start, end);
817
+ binding.invoke(event);
579
818
  }
580
819
  }
581
820
 
@@ -588,6 +827,14 @@ export class TextInputCore extends FlexBox {
588
827
  this.editorText.editable(!this.readOnlyValue && this.isEnabled);
589
828
  }
590
829
 
830
+ private syncBrowserInputMetadata(): void {
831
+ const handle = this.editorText.builtHandle;
832
+ if (handle == <u64>HandleValue.Invalid) {
833
+ return;
834
+ }
835
+ ui.registerTextInputMetadata(handle, this.passwordValue, this.hostAutofillHintValue);
836
+ }
837
+
591
838
  private syncSemanticLabel(): void {
592
839
  if (this.placeholderValue.length > 0) {
593
840
  this.editorText._defaultSemanticLabel(this.placeholderValue);
@@ -618,9 +865,26 @@ export class TextInputCore extends FlexBox {
618
865
  }
619
866
 
620
867
  private clampSelectionToText(): void {
621
- const length = this.textValue.length;
622
- this.selectionStartValue = clampSelection(this.selectionStartValue, length);
623
- this.selectionEndValue = clampSelection(this.selectionEndValue, length);
868
+ const length = codePointCount(this.textValue);
869
+ this.selectionStartValue = clampSelection(this.selectionStartValue, <i32>length);
870
+ this.selectionEndValue = clampSelection(this.selectionEndValue, <i32>length);
871
+ this.selectionStartByteValue = utf8ByteOffsetFromCodePointIndex(this.textValue, this.selectionStartValue);
872
+ this.selectionEndByteValue = utf8ByteOffsetFromCodePointIndex(this.textValue, this.selectionEndValue);
873
+ }
874
+
875
+ private selectionByteRange(start: u32, end: u32): this {
876
+ const length = utf8ByteLength(this.textValue);
877
+ const clampedStart = clampSelection(start, <i32>length);
878
+ const clampedEnd = clampSelection(end, <i32>length);
879
+ this.selectionStartByteValue = clampedStart;
880
+ this.selectionEndByteValue = clampedEnd;
881
+ this.selectionStartValue = codePointIndexFromUtf8ByteOffset(this.textValue, clampedStart);
882
+ this.selectionEndValue = codePointIndexFromUtf8ByteOffset(this.textValue, clampedEnd);
883
+ const handle = this.editorText.builtHandle;
884
+ if (handle != <u64>HandleValue.Invalid) {
885
+ ui.setTextSelectionRange(handle, clampedStart, clampedEnd);
886
+ }
887
+ return this;
624
888
  }
625
889
 
626
890
  focusEditor(): void {
@@ -635,7 +899,11 @@ export class TextInputCore extends FlexBox {
635
899
  return;
636
900
  }
637
901
  if (!this.profile.multiline) {
638
- this.focusEditor();
902
+ if (this.focusedState) {
903
+ this.focusEditor();
904
+ } else {
905
+ this.focusNow();
906
+ }
639
907
  return;
640
908
  }
641
909
  this.handleViewportPointerDown();
@@ -645,13 +913,11 @@ export class TextInputCore extends FlexBox {
645
913
  if (!this.isEnabled) {
646
914
  return;
647
915
  }
648
- const handle = this.editorText.builtHandle;
649
- if (handle == <u64>HandleValue.Invalid) {
916
+ if (this.focusedState) {
650
917
  this.focusEditor();
651
918
  return;
652
919
  }
653
- const documentEnd = utf8ByteLength(this.textValue);
654
- ui.setTextSelectionRange(handle, documentEnd, documentEnd);
920
+ this.focusNow();
655
921
  }
656
922
 
657
923
  private syncEditorWrapping(): void {
@@ -1,4 +1,4 @@
1
- import { BorderStyle, CursorStyle, Unit } from "../../core/ffi";
1
+ import { CursorStyle, Unit } from "../../core/ffi";
2
2
  import { Node } from "../../core/Node";
3
3
  import { Theme } from "../../core/Theme";
4
4
  import { FlexBox } from "../../nodes";
@@ -54,7 +54,7 @@ class DefaultTextInputPresenter extends TextInputPresenter {
54
54
  this.host
55
55
  .bgColor(bg)
56
56
  .cornerRadius(theme.spacing.sm)
57
- .border(1.0, borderColor, BorderStyle.Solid)
57
+ .border(1.0, borderColor)
58
58
  .padding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding)
59
59
  .cursor(shellCursor);
60
60
  this.host.opacity(state.enabled ? 1.0 : 0.6);
@@ -2,6 +2,8 @@ export {
2
2
  ButtonPresenter,
3
3
  ButtonTemplate,
4
4
  ButtonVisualState,
5
+ createDefaultButtonPresenter,
6
+ defaultButtonTemplate,
5
7
  } from "./internal/ButtonPresenter";
6
8
  export {
7
9
  ControlTemplateSet,
@@ -14,22 +16,30 @@ export {
14
16
  CheckboxIndicatorPresenter,
15
17
  CheckboxIndicatorTemplate,
16
18
  CheckboxIndicatorVisualState,
19
+ createDefaultCheckboxIndicatorPresenter,
20
+ defaultCheckboxIndicatorTemplate,
17
21
  } from "./internal/CheckboxIndicatorPresenter";
18
22
  export {
19
23
  RadioIndicatorPresenter,
20
24
  RadioIndicatorTemplate,
21
25
  RadioIndicatorVisualState,
26
+ createDefaultRadioIndicatorPresenter,
27
+ defaultRadioIndicatorTemplate,
22
28
  } from "./internal/RadioIndicatorPresenter";
23
29
  export {
24
30
  SwitchIndicatorPresenter,
25
31
  SwitchIndicatorTemplate,
26
32
  SwitchIndicatorVisualState,
33
+ createDefaultSwitchIndicatorPresenter,
34
+ defaultSwitchIndicatorTemplate,
27
35
  } from "./internal/SwitchIndicatorPresenter";
28
36
  export {
29
37
  SliderPresenter,
30
38
  SliderPresenterMetrics,
31
39
  SliderTemplate,
32
40
  SliderVisualState,
41
+ createDefaultSliderPresenter,
42
+ defaultSliderTemplate,
33
43
  } from "./internal/SliderPresenter";
34
44
  export {
35
45
  DropdownFieldMetrics,
@@ -3,6 +3,8 @@ import { ContextMenuManager } from "./ContextMenuManager";
3
3
  import { disposeAllFetchRequests } from "./Fetch";
4
4
  import { disposeAllFileRequests } from "./File";
5
5
  import { FocusAdornerManager } from "./FocusAdornerManager";
6
+ import { MobileTextSelectionToolbarManager } from "./MobileTextSelectionToolbarManager";
7
+ import { SelectionHandleAdornerManager } from "./SelectionHandleAdornerManager";
6
8
  import { ToolTipManager } from "./ToolTipManager";
7
9
  import * as ui from "../bindings/ui";
8
10
  import { flushCommit, fireLoadedCallbacks, markNeedsCommit, resetCommitState } from "./FrameScheduler";
@@ -29,12 +31,20 @@ function createEmptyPage(): Node {
29
31
  return new FlexBox().width(100.0, Unit.Percent).height(100.0, Unit.Percent);
30
32
  }
31
33
 
34
+ function disposeAppScopedAsyncWork(): void {
35
+ disposeAllFetchRequests();
36
+ disposeAllFileRequests();
37
+ disposeAllWorkers();
38
+ }
39
+
32
40
  class ApplicationShell extends FlexBox {
33
41
  constructor(root: Node) {
34
42
  super();
35
43
  this.width(100.0, Unit.Percent)
36
44
  .height(100.0, Unit.Percent)
37
45
  .child(root)
46
+ .child(SelectionHandleAdornerManager.createDefaultHost())
47
+ .child(MobileTextSelectionToolbarManager.createDefaultHost())
38
48
  .child(FocusAdornerManager.createDefaultHost())
39
49
  .child(ContextMenuManager.createDefaultMenu())
40
50
  .child(ToolTipManager.createDefaultHost());
@@ -198,10 +208,10 @@ export class Application<TPage> {
198
208
  }
199
209
 
200
210
  cancelAllTimers();
201
- disposeAllFetchRequests();
202
- disposeAllFileRequests();
203
- disposeAllWorkers();
211
+ disposeAppScopedAsyncWork();
204
212
  FocusAdornerManager.clear();
213
+ SelectionHandleAdornerManager.clear();
214
+ MobileTextSelectionToolbarManager.clear();
205
215
  ToolTipManager.clear();
206
216
  resetCommitState();
207
217
  ui.reset();
@@ -225,10 +235,10 @@ export class Application<TPage> {
225
235
  mountedShell = null;
226
236
  mountedRoot = null;
227
237
  cancelAllTimers();
228
- disposeAllFetchRequests();
229
- disposeAllFileRequests();
230
- disposeAllWorkers();
238
+ disposeAppScopedAsyncWork();
231
239
  FocusAdornerManager.clear();
240
+ SelectionHandleAdornerManager.clear();
241
+ MobileTextSelectionToolbarManager.clear();
232
242
  ToolTipManager.clear();
233
243
  resetCommitState();
234
244
  }
@@ -331,8 +341,7 @@ export function __disposeApp(): void {
331
341
  return;
332
342
  }
333
343
  app.dispose();
334
- disposeAllFetchRequests();
335
- disposeAllWorkers();
344
+ disposeAppScopedAsyncWork();
336
345
  }
337
346
 
338
347
  export function __flushRenders(): void {