@symbiote-native/svelte 0.1.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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -0
  3. package/build/bootstrap.d.ts +7 -0
  4. package/build/bootstrap.js +17 -0
  5. package/build/class-value.d.ts +6 -0
  6. package/build/class-value.js +81 -0
  7. package/build/components/RefreshControl.svelte +38 -0
  8. package/build/components/RefreshControl.svelte.d.ts +5 -0
  9. package/build/components/SafeAreaView.svelte +34 -0
  10. package/build/components/SafeAreaView.svelte.d.ts +5 -0
  11. package/build/components/Text.svelte +25 -0
  12. package/build/components/Text.svelte.d.ts +5 -0
  13. package/build/components/View.svelte +41 -0
  14. package/build/components/View.svelte.d.ts +5 -0
  15. package/build/components/activity-indicator/activity-indicator-platform.android.d.ts +3 -0
  16. package/build/components/activity-indicator/activity-indicator-platform.android.js +5 -0
  17. package/build/components/activity-indicator/activity-indicator-platform.d.ts +1 -0
  18. package/build/components/activity-indicator/activity-indicator-platform.ios.d.ts +3 -0
  19. package/build/components/activity-indicator/activity-indicator-platform.ios.js +6 -0
  20. package/build/components/activity-indicator/activity-indicator-platform.js +5 -0
  21. package/build/components/activity-indicator/activity-indicator-props.d.ts +5 -0
  22. package/build/components/activity-indicator/activity-indicator-props.js +1 -0
  23. package/build/components/activity-indicator/index.svelte +57 -0
  24. package/build/components/activity-indicator/index.svelte.d.ts +5 -0
  25. package/build/components/button-props.d.ts +5 -0
  26. package/build/components/button-props.js +1 -0
  27. package/build/components/button.svelte +71 -0
  28. package/build/components/button.svelte.d.ts +5 -0
  29. package/build/components/flat-list/flat-list-props.d.ts +10 -0
  30. package/build/components/flat-list/flat-list-props.js +1 -0
  31. package/build/components/flat-list/index.svelte +254 -0
  32. package/build/components/flat-list/index.svelte.d.ts +79 -0
  33. package/build/components/image/image-logic.d.ts +2 -0
  34. package/build/components/image/image-logic.js +125 -0
  35. package/build/components/image/image-props.d.ts +5 -0
  36. package/build/components/image/image-props.js +1 -0
  37. package/build/components/image/index.d.ts +3 -0
  38. package/build/components/image/index.js +10 -0
  39. package/build/components/image/index.svelte +66 -0
  40. package/build/components/image/index.svelte.d.ts +5 -0
  41. package/build/components/image-background/image-background-props.d.ts +10 -0
  42. package/build/components/image-background/image-background-props.js +1 -0
  43. package/build/components/image-background/index.svelte +81 -0
  44. package/build/components/image-background/index.svelte.d.ts +5 -0
  45. package/build/components/index.d.ts +50 -0
  46. package/build/components/index.js +24 -0
  47. package/build/components/input-accessory-view/index.svelte +48 -0
  48. package/build/components/input-accessory-view/index.svelte.d.ts +5 -0
  49. package/build/components/input-accessory-view/input-accessory-view-props.d.ts +11 -0
  50. package/build/components/input-accessory-view/input-accessory-view-props.js +1 -0
  51. package/build/components/keyboard-avoiding-view/index.svelte +135 -0
  52. package/build/components/keyboard-avoiding-view/index.svelte.d.ts +5 -0
  53. package/build/components/keyboard-avoiding-view/keyboard-avoiding-view-props.d.ts +15 -0
  54. package/build/components/keyboard-avoiding-view/keyboard-avoiding-view-props.js +1 -0
  55. package/build/components/modal/index.svelte +126 -0
  56. package/build/components/modal/index.svelte.d.ts +5 -0
  57. package/build/components/modal/modal-props.d.ts +24 -0
  58. package/build/components/modal/modal-props.js +1 -0
  59. package/build/components/pressable/index.svelte +160 -0
  60. package/build/components/pressable/index.svelte.d.ts +5 -0
  61. package/build/components/pressable/pressable-props.d.ts +27 -0
  62. package/build/components/pressable/pressable-props.js +1 -0
  63. package/build/components/refresh-control-props.d.ts +19 -0
  64. package/build/components/refresh-control-props.js +1 -0
  65. package/build/components/safe-area-view-props.d.ts +10 -0
  66. package/build/components/safe-area-view-props.js +1 -0
  67. package/build/components/scroll-view/index.svelte +298 -0
  68. package/build/components/scroll-view/index.svelte.d.ts +18 -0
  69. package/build/components/scroll-view/scroll-view-platform-types.d.ts +3 -0
  70. package/build/components/scroll-view/scroll-view-platform-types.js +1 -0
  71. package/build/components/scroll-view/scroll-view-platform.android.d.ts +3 -0
  72. package/build/components/scroll-view/scroll-view-platform.android.js +3 -0
  73. package/build/components/scroll-view/scroll-view-platform.d.ts +1 -0
  74. package/build/components/scroll-view/scroll-view-platform.ios.d.ts +3 -0
  75. package/build/components/scroll-view/scroll-view-platform.ios.js +3 -0
  76. package/build/components/scroll-view/scroll-view-platform.js +4 -0
  77. package/build/components/scroll-view/scroll-view-props.d.ts +78 -0
  78. package/build/components/scroll-view/scroll-view-props.js +1 -0
  79. package/build/components/scroll-view/scroll-view-sticky-context.d.ts +7 -0
  80. package/build/components/scroll-view/scroll-view-sticky-context.js +1 -0
  81. package/build/components/scroll-view/sticky-header-props.d.ts +10 -0
  82. package/build/components/scroll-view/sticky-header-props.js +1 -0
  83. package/build/components/scroll-view/sticky-header.svelte +254 -0
  84. package/build/components/scroll-view/sticky-header.svelte.d.ts +5 -0
  85. package/build/components/section-list/index.svelte +107 -0
  86. package/build/components/section-list/index.svelte.d.ts +57 -0
  87. package/build/components/section-list/section-list-props.d.ts +1 -0
  88. package/build/components/section-list/section-list-props.js +1 -0
  89. package/build/components/switch/index.svelte +123 -0
  90. package/build/components/switch/index.svelte.d.ts +5 -0
  91. package/build/components/switch/switch-platform-types.d.ts +5 -0
  92. package/build/components/switch/switch-platform-types.js +1 -0
  93. package/build/components/switch/switch-platform.android.d.ts +3 -0
  94. package/build/components/switch/switch-platform.android.js +8 -0
  95. package/build/components/switch/switch-platform.d.ts +1 -0
  96. package/build/components/switch/switch-platform.ios.d.ts +3 -0
  97. package/build/components/switch/switch-platform.ios.js +7 -0
  98. package/build/components/switch/switch-platform.js +5 -0
  99. package/build/components/switch/switch-props.d.ts +5 -0
  100. package/build/components/switch/switch-props.js +1 -0
  101. package/build/components/text-input/index.svelte +300 -0
  102. package/build/components/text-input/index.svelte.d.ts +12 -0
  103. package/build/components/text-input/text-input-props.d.ts +5 -0
  104. package/build/components/text-input/text-input-props.js +1 -0
  105. package/build/components/text-props.d.ts +23 -0
  106. package/build/components/text-props.js +1 -0
  107. package/build/components/touchable-base-props.d.ts +10 -0
  108. package/build/components/touchable-base-props.js +1 -0
  109. package/build/components/touchable-highlight/index.svelte +39 -0
  110. package/build/components/touchable-highlight/index.svelte.d.ts +5 -0
  111. package/build/components/touchable-highlight/touchable-highlight-props.d.ts +5 -0
  112. package/build/components/touchable-highlight/touchable-highlight-props.js +1 -0
  113. package/build/components/touchable-native-feedback/index.d.ts +9 -0
  114. package/build/components/touchable-native-feedback/index.js +14 -0
  115. package/build/components/touchable-native-feedback/touchable-native-feedback-props.d.ts +8 -0
  116. package/build/components/touchable-native-feedback/touchable-native-feedback-props.js +1 -0
  117. package/build/components/touchable-native-feedback/touchable-native-feedback.svelte +44 -0
  118. package/build/components/touchable-native-feedback/touchable-native-feedback.svelte.d.ts +5 -0
  119. package/build/components/touchable-opacity/index.svelte +124 -0
  120. package/build/components/touchable-opacity/index.svelte.d.ts +5 -0
  121. package/build/components/touchable-opacity/touchable-opacity-props.d.ts +5 -0
  122. package/build/components/touchable-opacity/touchable-opacity-props.js +1 -0
  123. package/build/components/touchable-without-feedback/index.svelte +19 -0
  124. package/build/components/touchable-without-feedback/index.svelte.d.ts +5 -0
  125. package/build/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +2 -0
  126. package/build/components/touchable-without-feedback/touchable-without-feedback-props.js +1 -0
  127. package/build/components/view-props.d.ts +29 -0
  128. package/build/components/view-props.js +1 -0
  129. package/build/components/virtualized-list/index.svelte +702 -0
  130. package/build/components/virtualized-list/index.svelte.d.ts +79 -0
  131. package/build/components/virtualized-list/virtualized-list-props.d.ts +70 -0
  132. package/build/components/virtualized-list/virtualized-list-props.js +73 -0
  133. package/build/components/virtualized-section-list/index.svelte +206 -0
  134. package/build/components/virtualized-section-list/index.svelte.d.ts +57 -0
  135. package/build/components/virtualized-section-list/virtualized-section-list-props.d.ts +62 -0
  136. package/build/components/virtualized-section-list/virtualized-section-list-props.js +1 -0
  137. package/build/create-tunnel/index.d.ts +3 -0
  138. package/build/create-tunnel/index.js +3 -0
  139. package/build/create-tunnel/tunnel-in.svelte +24 -0
  140. package/build/create-tunnel/tunnel-in.svelte.d.ts +9 -0
  141. package/build/create-tunnel/tunnel-out.svelte +14 -0
  142. package/build/create-tunnel/tunnel-out.svelte.d.ts +7 -0
  143. package/build/create-tunnel/tunnel.d.ts +9 -0
  144. package/build/create-tunnel/tunnel.js +9 -0
  145. package/build/descriptor-to-svelte.d.ts +7 -0
  146. package/build/descriptor-to-svelte.js +104 -0
  147. package/build/dom-shim/comment.d.ts +9 -0
  148. package/build/dom-shim/comment.js +24 -0
  149. package/build/dom-shim/document-fragment.d.ts +8 -0
  150. package/build/dom-shim/document-fragment.js +23 -0
  151. package/build/dom-shim/document.d.ts +23 -0
  152. package/build/dom-shim/document.js +61 -0
  153. package/build/dom-shim/element.d.ts +22 -0
  154. package/build/dom-shim/element.js +130 -0
  155. package/build/dom-shim/index.d.ts +5 -0
  156. package/build/dom-shim/index.js +7 -0
  157. package/build/dom-shim/patch-globals.d.ts +2 -0
  158. package/build/dom-shim/patch-globals.js +94 -0
  159. package/build/dom-shim/shim-node.d.ts +30 -0
  160. package/build/dom-shim/shim-node.js +207 -0
  161. package/build/dom-shim/text.d.ts +14 -0
  162. package/build/dom-shim/text.js +97 -0
  163. package/build/host-instance.d.ts +5 -0
  164. package/build/host-instance.js +48 -0
  165. package/build/index.d.ts +28 -0
  166. package/build/index.js +77 -0
  167. package/build/modules/accessibility-info/index.android.d.ts +2 -0
  168. package/build/modules/accessibility-info/index.android.js +4 -0
  169. package/build/modules/accessibility-info/index.d.ts +1 -0
  170. package/build/modules/accessibility-info/index.ios.d.ts +2 -0
  171. package/build/modules/accessibility-info/index.ios.js +3 -0
  172. package/build/modules/accessibility-info/index.js +5 -0
  173. package/build/modules/animated/AnimatedImage.svelte +115 -0
  174. package/build/modules/animated/AnimatedImage.svelte.d.ts +5 -0
  175. package/build/modules/animated/AnimatedScrollView.svelte +92 -0
  176. package/build/modules/animated/AnimatedScrollView.svelte.d.ts +17 -0
  177. package/build/modules/animated/AnimatedText.svelte +58 -0
  178. package/build/modules/animated/AnimatedText.svelte.d.ts +5 -0
  179. package/build/modules/animated/AnimatedView.svelte +95 -0
  180. package/build/modules/animated/AnimatedView.svelte.d.ts +5 -0
  181. package/build/modules/animated/animated-component-props.d.ts +7 -0
  182. package/build/modules/animated/animated-component-props.js +1 -0
  183. package/build/modules/animated/index.d.ts +58 -0
  184. package/build/modules/animated/index.js +69 -0
  185. package/build/modules/app-registry/index.d.ts +7 -0
  186. package/build/modules/app-registry/index.js +28 -0
  187. package/build/modules/status-bar/index.d.ts +5 -0
  188. package/build/modules/status-bar/index.js +21 -0
  189. package/build/modules/status-bar/index.svelte +20 -0
  190. package/build/modules/status-bar/index.svelte.d.ts +4 -0
  191. package/build/native-view-bridge.d.ts +4 -0
  192. package/build/native-view-bridge.js +27 -0
  193. package/build/preprocessor/forbid-web-only-constructs.d.ts +8 -0
  194. package/build/preprocessor/forbid-web-only-constructs.js +204 -0
  195. package/build/preprocessor/scoped-styles.d.ts +10 -0
  196. package/build/preprocessor/scoped-styles.js +399 -0
  197. package/build/render.d.ts +7 -0
  198. package/build/render.js +46 -0
  199. package/build/root-element.d.ts +3 -0
  200. package/build/root-element.js +27 -0
  201. package/build/runes/attachments.d.ts +5 -0
  202. package/build/runes/attachments.js +73 -0
  203. package/build/runes/dimensions-value.d.ts +4 -0
  204. package/build/runes/dimensions-value.js +30 -0
  205. package/build/runes/media-query.d.ts +8 -0
  206. package/build/runes/media-query.js +38 -0
  207. package/build/runes/use-color-scheme.svelte.d.ts +4 -0
  208. package/build/runes/use-color-scheme.svelte.js +25 -0
  209. package/build/runes/use-window-dimensions.svelte.d.ts +4 -0
  210. package/build/runes/use-window-dimensions.svelte.js +43 -0
  211. package/build/runes/window.d.ts +6 -0
  212. package/build/runes/window.js +28 -0
  213. package/build/scope-token.d.ts +2 -0
  214. package/build/scope-token.js +33 -0
  215. package/build/style-scope.d.ts +1 -0
  216. package/build/style-scope.js +29 -0
  217. package/metro-css-parser.cjs +9 -0
  218. package/metro-svelte-transformer.cjs +156 -0
  219. package/package.json +83 -0
@@ -0,0 +1,300 @@
1
+ <script lang="ts" module>
2
+ // TextInput: the controlled-value / event-count handshake, calling straight into
3
+ // core/components/src/view/render-text-input.ts's renderTextInput() for the prop assembly —
4
+ // its returned Descriptor's `.props` is destructured onto whichever of the two literal host
5
+ // tags `isMultiline` picks below. NOT `<svelte:element this={descriptor.type}>`: a dynamic tag
6
+ // compiles through Svelte's generic setAttribute path, not the custom-element `p=` property-set
7
+ // codegen every symbiote-* tag needs (proven by a real regression — see svelte-adapter-dom-shim
8
+ // skill §15). Reuses the shared logic verbatim (resolveTextInputProps / foldText /
9
+ // textFromChange / eventCountFromChange / shouldCommandText). Runes: `$state.raw` holds the
10
+ // shim-element/engine-node reference (imperative commands read the RAW ShimElement — see
11
+ // switch/index.svelte's header comment for why `$state()` would break the engine's
12
+ // WeakMap-keyed identity lookup), `$state` tracks the acknowledged event count, and `$effect`
13
+ // drives the controlled-write command — the TextInput twin of Switch's snap-back effect. The
14
+ // imperative handle (focus/blur/clear/isFocused/setSelection) is exposed the Svelte 5 way:
15
+ // plain functions declared in the INSTANCE script (not here in `<script module>`, shared
16
+ // across every instance) become callable off a parent's `bind:this` target.
17
+ //
18
+ // `value` is `$bindable()`: `<TextInput bind:value={x}>` round-trips a native edit into `x`
19
+ // with no adapter-side translation beyond the one-line echo in handleChange, GATED on
20
+ // `onValueChange` being absent. Ungated, it would defeat the controlled-write correction below
21
+ // for every plain `value`+`onValueChange` consumer too, not just bind: ones: a `$bindable`
22
+ // prop with no `bind:` caller still caches the child's own write as a local override (verified
23
+ // against svelte's real `prop()` runtime, `reactivity/props.js`), so an unconditional echo
24
+ // would make `value` agree with `lastNativeText` on every native report and silently
25
+ // short-circuit `shouldCommandText` — even for a parent that never once accepts.
26
+ import type { ITextInputProps } from './text-input-props';
27
+
28
+ export type { ITextInputProps };
29
+ export type { ITextInputHandle } from '@symbiote-native/components';
30
+ </script>
31
+
32
+ <script lang="ts">
33
+ import {
34
+ resolveAccessibilityProps,
35
+ resolveTextInputProps,
36
+ renderTextInput,
37
+ foldText,
38
+ textFromChange,
39
+ eventCountFromChange,
40
+ shouldCommandText,
41
+ INITIAL_EVENT_COUNT,
42
+ SELECTION_NONE,
43
+ } from '@symbiote-native/components';
44
+ import {
45
+ dispatchViewCommand,
46
+ dlog,
47
+ blurTextInput,
48
+ setInputFocused,
49
+ setInputBlurred,
50
+ type ISymbioteEvent,
51
+ } from '@symbiote-native/engine';
52
+ import { createDescriptorChildrenSync } from '../../descriptor-to-svelte';
53
+ import { createAttachmentsSync } from '../../runes/attachments';
54
+ import type { ShimElement } from '../../dom-shim';
55
+
56
+ // The exact destructure list React's index.ts pulls out of props before building
57
+ // `passthrough` (everything else rides through to Fabric untouched — placeholder,
58
+ // secureTextEntry, style, class, testID, the remaining native events, the accessibility*
59
+ // props already folded below). `class`/`style` are deliberately NOT here, same as Vue's
60
+ // HANDLED_ATTRS, so they forward like every other adapter's TextInput.
61
+ const HANDLED_KEYS: readonly string[] = [
62
+ 'defaultValue',
63
+ 'multiline',
64
+ 'selection',
65
+ 'onValueChange',
66
+ 'onFocus',
67
+ 'onBlur',
68
+ 'inputMode',
69
+ 'enterKeyHint',
70
+ 'readOnly',
71
+ 'submitBehavior',
72
+ 'blurOnSubmit',
73
+ 'cursorColor',
74
+ 'selectionColor',
75
+ 'selectionHandleColor',
76
+ 'keyboardType',
77
+ 'returnKeyType',
78
+ 'editable',
79
+ 'autoComplete',
80
+ 'textContentType',
81
+ 'autoFocus',
82
+ 'showSoftInputOnFocus',
83
+ 'underlineColorAndroid',
84
+ ];
85
+
86
+ // `resolveAccessibilityProps` only transforms accessibility*/aria-*/role fields, so it's
87
+ // applied HERE, once, at the one place that needs the folded result, rather than wrapped
88
+ // around the whole props object in an outer $derived — every other derived/effect keeps a
89
+ // plain, direct property read on the raw `$props()` proxy. `source` is a closed interface (no
90
+ // index signature), so `Object.keys` + bracket-indexing would need an `as` cast; `Object.entries`
91
+ // falls onto TS's `entries(o: {}): [string, any][]` overload instead, which needs no cast.
92
+ function forwardProps(source: Omit<ITextInputProps, 'value'>): Record<string, unknown> {
93
+ const resolved = resolveAccessibilityProps(source);
94
+ const result: Record<string, unknown> = {};
95
+ for (const [key, propValue] of Object.entries(resolved)) {
96
+ if (!HANDLED_KEYS.includes(key)) result[key] = propValue;
97
+ }
98
+ return result;
99
+ }
100
+
101
+ // `value` is $bindable() (see the module-script header for the full reasoning); everything
102
+ // else keeps flowing through `rest`, the exact twin of the old whole-object `rawProps` for
103
+ // every non-`value` field.
104
+ let { value = $bindable(), ...rest }: ITextInputProps = $props();
105
+
106
+ // $state.raw, NOT $state: identity concern, see the module-script header above.
107
+ let hostShim = $state.raw<ShimElement | null>(null);
108
+
109
+ // The count native last acknowledged. $state so the bag echoes it back on every commit and
110
+ // the exposed handle reads the latest (the Svelte twin of React's useState / Vue's ref).
111
+ let mostRecentEventCount = $state(INITIAL_EVENT_COUNT);
112
+ // The last text native holds, as far as JS knows. Seeded from the mount-time value — the
113
+ // `text` prop already carries it down via the bag, so the FIRST controlled value is not a
114
+ // divergence and must NOT re-command. Bookkeeping, not render state: a plain `let`, same as
115
+ // Vue's setup-scope `let lastNativeText`.
116
+ let lastNativeText = foldText(value, rest.defaultValue);
117
+ // JS-side focus state, mirrored from the focus/blur events for isFocused(): native exposes no
118
+ // synchronous focus getter (RN's TextInputState keeps the same).
119
+ let focused = false;
120
+ // autoFocus fires once, when the host node first goes live; guarded so a later re-render (or
121
+ // an intrinsic swap between single/multiline) doesn't re-focus.
122
+ let autoFocused = false;
123
+
124
+ const isMultiline = $derived(rest.multiline === true);
125
+ const text = $derived(foldText(value, rest.defaultValue));
126
+ const folded = $derived(
127
+ resolveTextInputProps({
128
+ inputMode: rest.inputMode,
129
+ keyboardType: rest.keyboardType,
130
+ enterKeyHint: rest.enterKeyHint,
131
+ returnKeyType: rest.returnKeyType,
132
+ readOnly: rest.readOnly,
133
+ editable: rest.editable,
134
+ submitBehavior: rest.submitBehavior,
135
+ blurOnSubmit: rest.blurOnSubmit,
136
+ multiline: isMultiline,
137
+ cursorColor: rest.cursorColor,
138
+ selectionColor: rest.selectionColor,
139
+ selectionHandleColor: rest.selectionHandleColor,
140
+ autoComplete: rest.autoComplete,
141
+ textContentType: rest.textContentType,
142
+ showSoftInputOnFocus: rest.showSoftInputOnFocus,
143
+ underlineColorAndroid: rest.underlineColorAndroid,
144
+ }),
145
+ );
146
+
147
+ function handleChange(event: ISymbioteEvent): void {
148
+ // Event seam: the controlled handshake hinges on the change payload carrying `text`
149
+ // (+ `eventCount`). iOS and Android Fabric can key these differently, so log the actual
150
+ // shape here; a missing `text` means onValueChange never fires.
151
+ dlog(
152
+ `TextInput change keys=[${Object.keys(event.nativeEvent).join(',')}] ` +
153
+ `text=${JSON.stringify(event.nativeEvent.text)} count=${JSON.stringify(event.nativeEvent.eventCount)}`,
154
+ );
155
+ const changedText = textFromChange(event);
156
+ if (changedText !== undefined) {
157
+ // Record the text first, then bump the acknowledged count, so the count never runs ahead
158
+ // of the text it stands for.
159
+ lastNativeText = changedText;
160
+ rest.onValueChange?.(changedText, event);
161
+ // $bindable() sugar: with no onValueChange nothing could reject this report, so mirror it
162
+ // straight into the bound value. A caller that ALSO supplies onValueChange keeps full
163
+ // accept/reject control via the controlled-write effect below — see the module-script
164
+ // header for why echoing unconditionally would break that for it too.
165
+ if (rest.onValueChange === undefined) value = changedText;
166
+ }
167
+ const count = eventCountFromChange(event);
168
+ if (count !== undefined) mostRecentEventCount = count;
169
+ }
170
+
171
+ function handleFocus(event: ISymbioteEvent): void {
172
+ focused = true;
173
+ // Track focus app-wide so Keyboard.dismiss can blur this input without a ref.
174
+ const engineNode = hostShim?.engineNode;
175
+ if (engineNode !== undefined) setInputFocused(engineNode);
176
+ rest.onFocus?.(event);
177
+ }
178
+
179
+ function handleBlur(event: ISymbioteEvent): void {
180
+ focused = false;
181
+ const engineNode = hostShim?.engineNode;
182
+ if (engineNode !== undefined) setInputBlurred(engineNode);
183
+ rest.onBlur?.(event);
184
+ }
185
+
186
+ // Controlled write: when JS-side `value` diverges from what native reported, command the new
187
+ // text down with the acknowledged count — a plain prop re-push would race the user's
188
+ // keystrokes; the command is the only stale-safe path. `value` and `count` are read
189
+ // UNCONDITIONALLY up front, before either early return: a rejecting parent never changes
190
+ // `props.value`, only `mostRecentEventCount` (bumped inside handleChange), so reading `count`
191
+ // only after the `shouldCommandText` guard would drop it from the tracked dependency set on the
192
+ // run that first falls through, and a later event-count-only change would then silently fail
193
+ // to retrigger this effect (same class of bug Switch's snap-back effect avoids). Verified
194
+ // against a real compiled mount (text-input.smoke.test.ts): the shim's insertOne() calls
195
+ // makeLive() synchronously as part of the SAME appendChild/insertBefore the compiler emits,
196
+ // before `bind:this` fires, so `hostShim.engineNode` is always populated by the time this
197
+ // first runs.
198
+ $effect(() => {
199
+ const engineNode = hostShim?.engineNode;
200
+ // `value` (component-scope, now $bindable()) IS the authoritative prop read — no local
201
+ // shadow needed; reading it here still establishes the effect's dependency exactly as the
202
+ // old `const value = rawProps.value` line did.
203
+ const currentValue = value;
204
+ const count = mostRecentEventCount;
205
+ if (engineNode === undefined) return;
206
+ if (!shouldCommandText(lastNativeText, currentValue)) return;
207
+ const selStart = rest.selection?.start ?? SELECTION_NONE;
208
+ const selEnd = rest.selection?.end ?? rest.selection?.start ?? SELECTION_NONE;
209
+ dlog(`TextInput setTextAndSelection count=${count} text=${JSON.stringify(currentValue)}`);
210
+ dispatchViewCommand(engineNode, 'setTextAndSelection', [count, currentValue, selStart, selEnd]);
211
+ lastNativeText = currentValue;
212
+ });
213
+
214
+ // autoFocus is driven in JS, not as a native prop: once the host node first goes live, command
215
+ // `focus` down once (RN does the same via TextInputState.focusInput). No commit-timing retry
216
+ // needed here (unlike Vue's whenCommitted) — same synchronous makeLive() guarantee the
217
+ // controlled-write effect above relies on.
218
+ $effect(() => {
219
+ const engineNode = hostShim?.engineNode;
220
+ if (autoFocused || engineNode === undefined || rest.autoFocus !== true) return;
221
+ autoFocused = true;
222
+ dlog('TextInput autoFocus -> focus command');
223
+ dispatchViewCommand(engineNode, 'focus', []);
224
+ });
225
+
226
+ // The imperative API RN exposes on a TextInput ref, as plain instance-script functions — the
227
+ // Svelte 5 mechanism (no useImperativeHandle/expose() rune): a parent's `bind:this={ref}` gets
228
+ // `ref.focus()` etc. callable directly, typed via the re-exported `ITextInputHandle` above.
229
+ // focus/blur drive native view commands; clear/setSelection reuse setTextAndSelection (the
230
+ // same stale-safe path as a controlled write), echoing the acknowledged event count.
231
+ export function focus(): void {
232
+ const engineNode = hostShim?.engineNode;
233
+ if (engineNode !== undefined) dispatchViewCommand(engineNode, 'focus', []);
234
+ }
235
+
236
+ export function blur(): void {
237
+ // Routes through TextInputState so the app-wide focus tracking clears too.
238
+ blurTextInput(hostShim?.engineNode ?? null);
239
+ }
240
+
241
+ export function clear(): void {
242
+ const engineNode = hostShim?.engineNode;
243
+ if (engineNode === undefined) return;
244
+ dispatchViewCommand(engineNode, 'setTextAndSelection', [mostRecentEventCount, '', 0, 0]);
245
+ lastNativeText = '';
246
+ }
247
+
248
+ export function isFocused(): boolean {
249
+ return focused;
250
+ }
251
+
252
+ export function setSelection(start: number, end: number): void {
253
+ const engineNode = hostShim?.engineNode;
254
+ if (engineNode === undefined) return;
255
+ const current = lastNativeText ?? '';
256
+ dispatchViewCommand(engineNode, 'setTextAndSelection', [
257
+ mostRecentEventCount,
258
+ current,
259
+ start,
260
+ end,
261
+ ]);
262
+ }
263
+
264
+ const descriptor = $derived(
265
+ renderTextInput({
266
+ multiline: isMultiline,
267
+ text,
268
+ mostRecentEventCount,
269
+ selection: rest.selection,
270
+ folded,
271
+ passthrough: {
272
+ ...forwardProps(rest),
273
+ onChange: handleChange,
274
+ onFocus: handleFocus,
275
+ onBlur: handleBlur,
276
+ },
277
+ }),
278
+ );
279
+
280
+ // renderTextInput()'s Descriptor has zero children (the whole surface is the `text`/
281
+ // `mostRecentEventCount` prop handshake) — wired anyway for the same uniform-shape reason
282
+ // Switch is, see its header comment.
283
+ const syncChildren = createDescriptorChildrenSync();
284
+ $effect(() => {
285
+ syncChildren(hostShim, descriptor.children);
286
+ });
287
+
288
+ // See View.svelte's note on `{@attach}`. `rest` (not `value`, which never carries a symbol
289
+ // key) is the same attachment-bearing bag `rawProps` used to be.
290
+ const syncAttachments = createAttachmentsSync();
291
+ $effect(() => {
292
+ syncAttachments(hostShim, rest);
293
+ });
294
+ </script>
295
+
296
+ {#if isMultiline}
297
+ <symbiote-text-input-multiline p={descriptor.props} bind:this={hostShim} />
298
+ {:else}
299
+ <symbiote-text-input p={descriptor.props} bind:this={hostShim} />
300
+ {/if}
@@ -0,0 +1,12 @@
1
+ import type { ITextInputProps } from './text-input-props';
2
+ export type { ITextInputProps };
3
+ export type { ITextInputHandle } from '@symbiote-native/components';
4
+ declare const Index: import("svelte").Component<ITextInputProps, {
5
+ focus: () => void;
6
+ blur: () => void;
7
+ clear: () => void;
8
+ isFocused: () => boolean;
9
+ setSelection: (start: number, end: number) => void;
10
+ }, "value">;
11
+ type Index = ReturnType<typeof Index>;
12
+ export default Index;
@@ -0,0 +1,5 @@
1
+ import type { ITextInputProps as ITextInputBaseProps } from '@symbiote-native/components';
2
+ import type { ISvelteClassValue } from '../../class-value';
3
+ export type ITextInputProps = ITextInputBaseProps & {
4
+ class?: ISvelteClassValue;
5
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { ISymbioteEvent, IStyleProp, ITextStyle } from '@symbiote-native/engine';
3
+ import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
4
+ import type { ISvelteClassValue } from '../class-value';
5
+ export interface ITextProps extends IAccessibilityProps, IAriaProps {
6
+ style?: IStyleProp<ITextStyle>;
7
+ class?: ISvelteClassValue;
8
+ onPress?: (event: ISymbioteEvent) => void;
9
+ onLongPress?: (event: ISymbioteEvent) => void;
10
+ onPressIn?: (event: ISymbioteEvent) => void;
11
+ onPressOut?: (event: ISymbioteEvent) => void;
12
+ onLayout?: (event: ISymbioteEvent) => void;
13
+ onTextLayout?: (event: ISymbioteEvent) => void;
14
+ numberOfLines?: number;
15
+ ellipsizeMode?: 'head' | 'middle' | 'tail' | 'clip';
16
+ selectable?: boolean;
17
+ adjustsFontSizeToFit?: boolean;
18
+ minimumFontScale?: number;
19
+ allowFontScaling?: boolean;
20
+ maxFontSizeMultiplier?: number | null;
21
+ selectionColor?: string;
22
+ children?: Snippet;
23
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { IStyleProp, IViewStyle } from '@symbiote-native/engine';
3
+ import type { IPressTimingProps } from '@symbiote-native/components';
4
+ import type { IPressableProps } from './pressable/pressable-props';
5
+ import type { ISvelteClassValue } from '../class-value';
6
+ export type ITouchableBaseProps = Omit<IPressableProps, 'style' | 'class' | 'children'> & IPressTimingProps & {
7
+ style?: IStyleProp<IViewStyle>;
8
+ class?: ISvelteClassValue;
9
+ children?: Snippet;
10
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ <script lang="ts" module>
2
+ // TouchableHighlight: built on Pressable. RN drives the underlay with setState (not Animated —
3
+ // TouchableHighlight.js), so unlike TouchableOpacity this needs no tween: `highlightPressedStyle`
4
+ // (@symbiote-native/components/view/render-touchable-highlight) is a pure function of the live
5
+ // `pressed` flag, fed through Pressable's own style-as-function-of-state prop — the same
6
+ // mechanism React's `pressedStyle` callback and Vue's style-as-function attr use.
7
+ import type { ITouchableHighlightProps } from './touchable-highlight-props';
8
+
9
+ export type { ITouchableHighlightProps };
10
+ </script>
11
+
12
+ <script lang="ts">
13
+ import {
14
+ highlightPressedStyle,
15
+ DEFAULT_HIGHLIGHT_CHILD_OPACITY,
16
+ DEFAULT_UNDERLAY_COLOR,
17
+ type IPressState,
18
+ } from '@symbiote-native/components';
19
+ import type { IStyleProp, IViewStyle } from '@symbiote-native/engine';
20
+ import Pressable from '../pressable/index.svelte';
21
+
22
+ let {
23
+ activeOpacity = DEFAULT_HIGHLIGHT_CHILD_OPACITY,
24
+ underlayColor = DEFAULT_UNDERLAY_COLOR,
25
+ style,
26
+ children: content,
27
+ ...rest
28
+ }: ITouchableHighlightProps = $props();
29
+
30
+ function pressedStyle({ pressed }: IPressState): IStyleProp<IViewStyle> {
31
+ return highlightPressedStyle(pressed, style, underlayColor, activeOpacity);
32
+ }
33
+ </script>
34
+
35
+ <Pressable {...rest} style={pressedStyle}>
36
+ {#snippet children()}
37
+ {@render content?.()}
38
+ {/snippet}
39
+ </Pressable>
@@ -0,0 +1,5 @@
1
+ import type { ITouchableHighlightProps } from './touchable-highlight-props';
2
+ export type { ITouchableHighlightProps };
3
+ declare const Index: import("svelte").Component<ITouchableHighlightProps, {}, "">;
4
+ type Index = ReturnType<typeof Index>;
5
+ export default Index;
@@ -0,0 +1,5 @@
1
+ import type { ITouchableBaseProps } from '../touchable-base-props';
2
+ export interface ITouchableHighlightProps extends ITouchableBaseProps {
3
+ activeOpacity?: number;
4
+ underlayColor?: string;
5
+ }
@@ -0,0 +1,9 @@
1
+ import { canUseNativeForeground, rippleBackground, selectableBackground, selectableBackgroundBorderless } from '@symbiote-native/components';
2
+ export type { ITouchableNativeFeedbackProps } from './touchable-native-feedback-props';
3
+ export type { INativeFeedbackBackground, IThemeAttrBackground, IRippleBackground, } from '@symbiote-native/components';
4
+ export declare const TouchableNativeFeedback: import("svelte").Component<import("./touchable-native-feedback-props").ITouchableNativeFeedbackProps, {}, ""> & {
5
+ SelectableBackground: typeof selectableBackground;
6
+ SelectableBackgroundBorderless: typeof selectableBackgroundBorderless;
7
+ Ripple: typeof rippleBackground;
8
+ canUseNativeForeground: typeof canUseNativeForeground;
9
+ };
@@ -0,0 +1,14 @@
1
+ // TouchableNativeFeedback's static factories (SelectableBackground/SelectableBackgroundBorderless/
2
+ // Ripple/canUseNativeForeground) mirror RN's own API surface and must live ON the component
3
+ // value — callers reach `TouchableNativeFeedback.Ripple(...)`, exactly like React's
4
+ // `Object.assign(TouchableNativeFeedbackImpl, {...})` and Vue's identical pattern. A `.svelte`
5
+ // file's own `<script module>` block cannot reassign or wrap its own compiled default export, so
6
+ // the statics are attached here, one level up, over the imported component.
7
+ import { canUseNativeForeground, rippleBackground, selectableBackground, selectableBackgroundBorderless, } from '@symbiote-native/components';
8
+ import TouchableNativeFeedbackImpl from './touchable-native-feedback.svelte';
9
+ export const TouchableNativeFeedback = Object.assign(TouchableNativeFeedbackImpl, {
10
+ SelectableBackground: selectableBackground,
11
+ SelectableBackgroundBorderless: selectableBackgroundBorderless,
12
+ Ripple: rippleBackground,
13
+ canUseNativeForeground,
14
+ });
@@ -0,0 +1,8 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { INativeFeedbackBackground } from '@symbiote-native/components';
3
+ import type { IPressableProps } from '../pressable/pressable-props';
4
+ export type ITouchableNativeFeedbackProps = Omit<IPressableProps, 'style' | 'children'> & {
5
+ background?: INativeFeedbackBackground;
6
+ useForeground?: boolean;
7
+ children?: Snippet;
8
+ };
@@ -0,0 +1,44 @@
1
+ <script lang="ts" module>
2
+ // TouchableNativeFeedback: Android's ripple/state-drawable touchable, built on Pressable like
3
+ // the rest of the family. The native ripple props (nativeBackgroundAndroid /
4
+ // nativeForegroundAndroid) ride a dedicated feedback View nested under the Pressable; on iOS
5
+ // they are inert. The static factories + background mapping are shared in
6
+ // @symbiote-native/components/view — this component only attaches them onto the component
7
+ // value (see this folder's index.ts, which Object.assigns them — a `.svelte` file's own
8
+ // `<script module>` cannot reassign or wrap its own compiled default export) and nests the
9
+ // feedback View.
10
+ import type { ITouchableNativeFeedbackProps } from './touchable-native-feedback-props';
11
+
12
+ export type { ITouchableNativeFeedbackProps };
13
+ </script>
14
+
15
+ <script lang="ts">
16
+ import { backgroundProps, selectableBackground } from '@symbiote-native/components';
17
+ import { dlog } from '@symbiote-native/engine';
18
+ import Pressable from '../pressable/index.svelte';
19
+
20
+ let {
21
+ background,
22
+ useForeground = false,
23
+ children: content,
24
+ ...rest
25
+ }: ITouchableNativeFeedbackProps = $props();
26
+
27
+ // RN defaults a missing background to SelectableBackground() so the touchable always shows
28
+ // feedback; mirror that here.
29
+ const resolved = $derived(background ?? selectableBackground());
30
+
31
+ $effect(() => {
32
+ dlog(`TouchableNativeFeedback render ${resolved.type} useForeground ${useForeground}`);
33
+ });
34
+
35
+ const nativeProps = $derived(backgroundProps(resolved, useForeground));
36
+ </script>
37
+
38
+ <Pressable {...rest}>
39
+ {#snippet children()}
40
+ <symbiote-view p={nativeProps}>
41
+ {@render content?.()}
42
+ </symbiote-view>
43
+ {/snippet}
44
+ </Pressable>
@@ -0,0 +1,5 @@
1
+ import type { ITouchableNativeFeedbackProps } from './touchable-native-feedback-props';
2
+ export type { ITouchableNativeFeedbackProps };
3
+ declare const TouchableNativeFeedback: import("svelte").Component<ITouchableNativeFeedbackProps, {}, "">;
4
+ type TouchableNativeFeedback = ReturnType<typeof TouchableNativeFeedback>;
5
+ export default TouchableNativeFeedback;
@@ -0,0 +1,124 @@
1
+ <script lang="ts" module>
2
+ // TouchableOpacity: built on Pressable, like the rest of the family. RN realizes its feedback
3
+ // with Animated (an opacity fade to activeOpacity on press-in, back to 1 on press-out); the
4
+ // press-timing constants and the deactivation-floor math are shared with every adapter
5
+ // (@symbiote-native/components/state/touchable). No `Animated.View` binding exists yet for
6
+ // this adapter — core/engine/src/animated is the framework-agnostic value-graph plumbing, but
7
+ // React and Vue each wrote their OWN reactive binding on top of it
8
+ // (adapters/react/src/modules/animated, adapters/vue/src/modules/animated); porting that
9
+ // binding to Svelte runes is separate, standalone work, out of scope for this Pressable/
10
+ // Touchable/Button pass. `tweenOpacity` below is a deliberate, documented stand-in: it
11
+ // reproduces Animated.timing's quad-inOut curve over the SAME shared duration constants
12
+ // directly onto the style bag via `$state`, using `setTimeout` (not `requestAnimationFrame`,
13
+ // which this adapter must never patch globally per the dom-shim skill §6c, and which a bare
14
+ // headless test sandbox does not provide) so TouchableOpacity still visibly fades. It is not
15
+ // wired into the native Animated node graph — `useNativeDriver`-style native-thread driving is
16
+ // NOT available here until a real Animated adapter binding lands.
17
+ import type { ITouchableOpacityProps } from './touchable-opacity-props';
18
+
19
+ export type { ITouchableOpacityProps };
20
+ </script>
21
+
22
+ <script lang="ts">
23
+ import {
24
+ createTouchableFeedbackHandlers,
25
+ createTouchableFeedbackRuntime,
26
+ DEFAULT_ACTIVE_OPACITY,
27
+ DEFAULT_MIN_PRESS_DURATION_MS,
28
+ OPACITY_ACTIVE_DURATION_MS,
29
+ OPACITY_INACTIVE_DURATION_MS,
30
+ RESTING_OPACITY,
31
+ } from '@symbiote-native/components';
32
+ import type { ISymbioteEvent } from '@symbiote-native/engine';
33
+ import Pressable from '../pressable/index.svelte';
34
+
35
+ const TWEEN_FRAME_MS = 16;
36
+
37
+ // See the module header above: the interim substitute for a real Animated.View driver.
38
+ function tweenOpacity(
39
+ from: number,
40
+ to: number,
41
+ durationMs: number,
42
+ onFrame: (value: number) => void,
43
+ ): () => void {
44
+ const start = Date.now();
45
+ let cancelled = false;
46
+ function step(): void {
47
+ if (cancelled) return;
48
+ const t = Math.min(1, (Date.now() - start) / durationMs);
49
+ const eased = t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2;
50
+ onFrame(from + (to - from) * eased);
51
+ if (t < 1) setTimeout(step, TWEEN_FRAME_MS);
52
+ }
53
+ step();
54
+ return () => {
55
+ cancelled = true;
56
+ };
57
+ }
58
+
59
+ function scheduleTimeout(callback: () => void, ms: number): () => void {
60
+ const id = setTimeout(callback, ms);
61
+ return () => clearTimeout(id);
62
+ }
63
+
64
+ let {
65
+ activeOpacity = DEFAULT_ACTIVE_OPACITY,
66
+ style,
67
+ class: className,
68
+ children: content,
69
+ onPressIn,
70
+ onPressOut,
71
+ delayPressIn = 0,
72
+ delayPressOut = 0,
73
+ minPressDuration = DEFAULT_MIN_PRESS_DURATION_MS,
74
+ ...rest
75
+ }: ITouchableOpacityProps = $props();
76
+
77
+ let opacity = $state(RESTING_OPACITY);
78
+ let cancelTween: (() => void) | undefined;
79
+ // Setup-scope only, mutated by the machine — same as Pressable's own runtime, never `$state`.
80
+ const runtime = createTouchableFeedbackRuntime();
81
+
82
+ function setOpacityTo(toValue: number, duration: number): void {
83
+ cancelTween?.();
84
+ const from = opacity;
85
+ cancelTween = tweenOpacity(from, toValue, duration, value => {
86
+ opacity = value;
87
+ });
88
+ }
89
+
90
+ // Rebuilt whenever the timing config changes (mirrors Vue's per-render rebuild); the runtime
91
+ // persists across rebuilds so an in-flight delayPressIn timer / activation clock survives.
92
+ const handlers = $derived(
93
+ createTouchableFeedbackHandlers(
94
+ {
95
+ delayPressIn,
96
+ delayPressOut,
97
+ minPressDuration,
98
+ schedule: scheduleTimeout,
99
+ now: Date.now,
100
+ },
101
+ runtime,
102
+ {
103
+ activate(event: ISymbioteEvent): void {
104
+ setOpacityTo(activeOpacity, OPACITY_ACTIVE_DURATION_MS);
105
+ onPressIn?.(event);
106
+ },
107
+ deactivate(event: ISymbioteEvent): void {
108
+ setOpacityTo(RESTING_OPACITY, OPACITY_INACTIVE_DURATION_MS);
109
+ onPressOut?.(event);
110
+ },
111
+ },
112
+ ),
113
+ );
114
+
115
+ const foldedStyle = $derived([style, { opacity }]);
116
+ </script>
117
+
118
+ <Pressable {...rest} onPressIn={handlers.handlePressIn} onPressOut={handlers.handlePressOut}>
119
+ {#snippet children()}
120
+ <symbiote-view p={{ style: foldedStyle, class: className }}>
121
+ {@render content?.()}
122
+ </symbiote-view>
123
+ {/snippet}
124
+ </Pressable>
@@ -0,0 +1,5 @@
1
+ import type { ITouchableOpacityProps } from './touchable-opacity-props';
2
+ export type { ITouchableOpacityProps };
3
+ declare const Index: import("svelte").Component<ITouchableOpacityProps, {}, "">;
4
+ type Index = ReturnType<typeof Index>;
5
+ export default Index;
@@ -0,0 +1,5 @@
1
+ import type { ITouchableBaseProps } from '../touchable-base-props';
2
+ export type { ITouchableBaseProps };
3
+ export interface ITouchableOpacityProps extends ITouchableBaseProps {
4
+ activeOpacity?: number;
5
+ }