@symbiote-native/svelte 1.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -13
- package/build/components/{activity-indicator/activity-indicator-props.d.ts → activity-indicator-props.d.ts} +1 -1
- package/build/components/flat-list/index.svelte +9 -5
- package/build/components/{image-background/image-background-props.d.ts → image-background-props.d.ts} +1 -1
- package/build/components/index.d.ts +6 -24
- package/build/components/index.js +0 -18
- package/build/components/keyboard-avoiding-view/index.svelte +16 -7
- package/build/components/modal/index.svelte +7 -7
- package/build/components/pressable/pressable-props.d.ts +3 -0
- package/build/components/refresh-control-props.d.ts +1 -0
- package/build/components/text-props.d.ts +1 -0
- package/build/components/touchable-highlight/touchable-highlight-props.d.ts +2 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback-props.d.ts +1 -0
- package/build/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +3 -1
- package/build/components/virtualized-list/index.svelte +79 -257
- package/build/components/virtualized-list/virtualized-list-props.d.ts +2 -2
- package/build/components/virtualized-list/virtualized-list-props.js +24 -4
- package/build/create-portal/index.js +2 -2
- package/build/dom-shim/canonical-prop-names.d.ts +2 -0
- package/build/dom-shim/canonical-prop-names.js +323 -0
- package/build/dom-shim/document.js +1 -1
- package/build/dom-shim/element.d.ts +10 -3
- package/build/dom-shim/element.js +233 -31
- package/build/dom-shim/patch-globals.js +31 -5
- package/build/dom-shim/shim-node.d.ts +6 -0
- package/build/dom-shim/shim-node.js +48 -1
- package/build/dom-shim/style-cache.d.ts +1 -0
- package/build/dom-shim/style-cache.js +27 -0
- package/build/dom-shim/text.js +5 -4
- package/build/host-instance.d.ts +2 -3
- package/build/host-instance.js +9 -3
- package/build/index.d.ts +7 -4
- package/build/index.js +17 -5
- package/build/intrinsic-elements.d.ts +63 -0
- package/build/modules/animated/index.d.ts +30 -46
- package/build/modules/animated/index.js +24 -34
- package/build/modules/image/index.d.ts +3 -0
- package/build/modules/image/index.js +15 -0
- package/build/preprocessor/scoped-styles.js +1 -1
- package/build/register.d.ts +1 -1
- package/build/register.js +13 -31
- package/build/root-element.js +1 -1
- package/metro-css-parser.cjs +2 -1
- package/metro-svelte-transformer.cjs +1 -17
- package/package.json +8 -16
- package/build/components/RefreshControl.svelte +0 -40
- package/build/components/RefreshControl.svelte.d.ts +0 -5
- package/build/components/SafeAreaView.svelte +0 -34
- package/build/components/SafeAreaView.svelte.d.ts +0 -5
- package/build/components/Text.svelte +0 -34
- package/build/components/Text.svelte.d.ts +0 -5
- package/build/components/View.svelte +0 -43
- package/build/components/View.svelte.d.ts +0 -5
- package/build/components/activity-indicator/activity-indicator-platform.android.d.ts +0 -3
- package/build/components/activity-indicator/activity-indicator-platform.android.js +0 -5
- package/build/components/activity-indicator/activity-indicator-platform.d.ts +0 -1
- package/build/components/activity-indicator/activity-indicator-platform.ios.d.ts +0 -3
- package/build/components/activity-indicator/activity-indicator-platform.ios.js +0 -6
- package/build/components/activity-indicator/activity-indicator-platform.js +0 -5
- package/build/components/activity-indicator/index.svelte +0 -63
- package/build/components/activity-indicator/index.svelte.d.ts +0 -5
- package/build/components/button.svelte +0 -76
- package/build/components/button.svelte.d.ts +0 -5
- package/build/components/image/image-logic.d.ts +0 -2
- package/build/components/image/image-logic.js +0 -135
- package/build/components/image/index.d.ts +0 -3
- package/build/components/image/index.js +0 -10
- package/build/components/image/index.svelte +0 -66
- package/build/components/image/index.svelte.d.ts +0 -5
- package/build/components/image-background/index.svelte +0 -87
- package/build/components/image-background/index.svelte.d.ts +0 -5
- package/build/components/input-accessory-view/index.svelte +0 -51
- package/build/components/input-accessory-view/index.svelte.d.ts +0 -5
- package/build/components/pressable/index.svelte +0 -185
- package/build/components/pressable/index.svelte.d.ts +0 -9
- package/build/components/scroll-view/index.svelte +0 -331
- package/build/components/scroll-view/index.svelte.d.ts +0 -18
- package/build/components/scroll-view/scroll-view-platform-types.d.ts +0 -3
- package/build/components/scroll-view/scroll-view-platform.android.d.ts +0 -3
- package/build/components/scroll-view/scroll-view-platform.android.js +0 -3
- package/build/components/scroll-view/scroll-view-platform.d.ts +0 -1
- package/build/components/scroll-view/scroll-view-platform.ios.d.ts +0 -3
- package/build/components/scroll-view/scroll-view-platform.ios.js +0 -3
- package/build/components/scroll-view/scroll-view-platform.js +0 -4
- package/build/components/scroll-view/scroll-view-sticky-context.d.ts +0 -7
- package/build/components/scroll-view/scroll-view-sticky-context.js +0 -1
- package/build/components/scroll-view/sticky-header-props.d.ts +0 -10
- package/build/components/scroll-view/sticky-header-props.js +0 -1
- package/build/components/scroll-view/sticky-header.svelte +0 -287
- package/build/components/scroll-view/sticky-header.svelte.d.ts +0 -5
- package/build/components/switch/index.svelte +0 -134
- package/build/components/switch/index.svelte.d.ts +0 -5
- package/build/components/text-input/index.svelte +0 -372
- package/build/components/text-input/index.svelte.d.ts +0 -17
- package/build/components/touchable-highlight/index.svelte +0 -139
- package/build/components/touchable-highlight/index.svelte.d.ts +0 -5
- package/build/components/touchable-native-feedback/index.d.ts +0 -9
- package/build/components/touchable-native-feedback/index.js +0 -14
- package/build/components/touchable-native-feedback/touchable-native-feedback.svelte +0 -49
- package/build/components/touchable-native-feedback/touchable-native-feedback.svelte.d.ts +0 -5
- package/build/components/touchable-opacity/index.svelte +0 -181
- package/build/components/touchable-opacity/index.svelte.d.ts +0 -5
- package/build/components/touchable-without-feedback/index.svelte +0 -75
- package/build/components/touchable-without-feedback/index.svelte.d.ts +0 -5
- package/build/dom-shim/fold-host-bag.d.ts +0 -1
- package/build/dom-shim/fold-host-bag.js +0 -7
- package/build/modules/animated/animated-component-props.d.ts +0 -7
- package/build/modules/animated/animated-component-props.js +0 -1
- package/build/modules/animated/create-animated-component.d.ts +0 -5
- package/build/modules/animated/create-animated-component.js +0 -131
- package/build/preprocessor/lower-host-primitives.d.ts +0 -10
- package/build/preprocessor/lower-host-primitives.js +0 -455
- package/build/state-style.d.ts +0 -1
- package/build/state-style.js +0 -8
- /package/build/components/{activity-indicator/activity-indicator-props.js → activity-indicator-props.js} +0 -0
- /package/build/components/{image-background/image-background-props.js → image-background-props.js} +0 -0
- /package/build/{components/scroll-view/scroll-view-platform-types.js → intrinsic-elements.js} +0 -0
|
@@ -1,372 +0,0 @@
|
|
|
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
|
-
type ISymbioteNode,
|
|
52
|
-
type IMeasureOnSuccess,
|
|
53
|
-
type IMeasureInWindowOnSuccess,
|
|
54
|
-
type IMeasureLayoutOnSuccess,
|
|
55
|
-
} from '@symbiote-native/engine';
|
|
56
|
-
import { createDescriptorChildrenSync } from '../../descriptor-to-svelte';
|
|
57
|
-
import { createAttachmentsSync } from '../../runes/attachments';
|
|
58
|
-
import type { ShimElement } from '../../dom-shim';
|
|
59
|
-
|
|
60
|
-
// The exact destructure list React's index.ts pulls out of props before building
|
|
61
|
-
// `passthrough` (everything else rides through to Fabric untouched — placeholder,
|
|
62
|
-
// secureTextEntry, style, class, testID, the remaining native events, the accessibility*
|
|
63
|
-
// props already folded below). `class`/`style` are deliberately NOT here, same as Vue's
|
|
64
|
-
// HANDLED_ATTRS, so they forward like every other adapter's TextInput.
|
|
65
|
-
const HANDLED_KEYS: readonly string[] = [
|
|
66
|
-
'defaultValue',
|
|
67
|
-
'multiline',
|
|
68
|
-
'selection',
|
|
69
|
-
'onValueChange',
|
|
70
|
-
'onFocus',
|
|
71
|
-
'onBlur',
|
|
72
|
-
'inputMode',
|
|
73
|
-
'enterKeyHint',
|
|
74
|
-
'readOnly',
|
|
75
|
-
'submitBehavior',
|
|
76
|
-
'blurOnSubmit',
|
|
77
|
-
'cursorColor',
|
|
78
|
-
'selectionColor',
|
|
79
|
-
'selectionHandleColor',
|
|
80
|
-
'keyboardType',
|
|
81
|
-
'returnKeyType',
|
|
82
|
-
'editable',
|
|
83
|
-
'autoComplete',
|
|
84
|
-
'textContentType',
|
|
85
|
-
'autoFocus',
|
|
86
|
-
'showSoftInputOnFocus',
|
|
87
|
-
'underlineColorAndroid',
|
|
88
|
-
];
|
|
89
|
-
|
|
90
|
-
// `resolveAccessibilityProps` only transforms accessibility*/aria-*/role fields, so it's
|
|
91
|
-
// applied HERE, once, at the one place that needs the folded result, rather than wrapped
|
|
92
|
-
// around the whole props object in an outer $derived — every other derived/effect keeps a
|
|
93
|
-
// plain, direct property read on the raw `$props()` proxy. `source` is a closed interface (no
|
|
94
|
-
// index signature), so `Object.keys` + bracket-indexing would need an `as` cast; `Object.entries`
|
|
95
|
-
// falls onto TS's `entries(o: {}): [string, any][]` overload instead, which needs no cast.
|
|
96
|
-
function forwardProps(
|
|
97
|
-
source: Omit<ITextInputProps, 'value'>,
|
|
98
|
-
): Record<string, unknown> {
|
|
99
|
-
const resolved = resolveAccessibilityProps(source);
|
|
100
|
-
const result: Record<string, unknown> = {};
|
|
101
|
-
for (const [key, propValue] of Object.entries(resolved)) {
|
|
102
|
-
if (!HANDLED_KEYS.includes(key)) result[key] = propValue;
|
|
103
|
-
}
|
|
104
|
-
return result;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// `value` is $bindable() (see the module-script header for the full reasoning); everything
|
|
108
|
-
// else keeps flowing through `rest`, the exact twin of the old whole-object `rawProps` for
|
|
109
|
-
// every non-`value` field.
|
|
110
|
-
let { value = $bindable(), ...rest }: ITextInputProps = $props();
|
|
111
|
-
|
|
112
|
-
// $state.raw, NOT $state: identity concern, see the module-script header above.
|
|
113
|
-
let hostShim = $state.raw<ShimElement | null>(null);
|
|
114
|
-
|
|
115
|
-
// The count native last acknowledged. $state so the bag echoes it back on every commit and
|
|
116
|
-
// the exposed handle reads the latest (the Svelte twin of React's useState / Vue's ref).
|
|
117
|
-
let mostRecentEventCount = $state(INITIAL_EVENT_COUNT);
|
|
118
|
-
// The last text native holds, as far as JS knows. Seeded from the mount-time value — the
|
|
119
|
-
// `text` prop already carries it down via the bag, so the FIRST controlled value is not a
|
|
120
|
-
// divergence and must NOT re-command. Bookkeeping, not render state: a plain `let`, same as
|
|
121
|
-
// Vue's setup-scope `let lastNativeText`.
|
|
122
|
-
let lastNativeText = foldText(value, rest.defaultValue);
|
|
123
|
-
// JS-side focus state, mirrored from the focus/blur events for isFocused(): native exposes no
|
|
124
|
-
// synchronous focus getter (RN's TextInputState keeps the same).
|
|
125
|
-
let focused = false;
|
|
126
|
-
// autoFocus fires once, when the host node first goes live; guarded so a later re-render (or
|
|
127
|
-
// an intrinsic swap between single/multiline) doesn't re-focus.
|
|
128
|
-
let autoFocused = false;
|
|
129
|
-
|
|
130
|
-
const isMultiline = $derived(rest.multiline === true);
|
|
131
|
-
const text = $derived(foldText(value, rest.defaultValue));
|
|
132
|
-
const folded = $derived(
|
|
133
|
-
resolveTextInputProps({
|
|
134
|
-
inputMode: rest.inputMode,
|
|
135
|
-
keyboardType: rest.keyboardType,
|
|
136
|
-
enterKeyHint: rest.enterKeyHint,
|
|
137
|
-
returnKeyType: rest.returnKeyType,
|
|
138
|
-
readOnly: rest.readOnly,
|
|
139
|
-
editable: rest.editable,
|
|
140
|
-
submitBehavior: rest.submitBehavior,
|
|
141
|
-
blurOnSubmit: rest.blurOnSubmit,
|
|
142
|
-
multiline: isMultiline,
|
|
143
|
-
cursorColor: rest.cursorColor,
|
|
144
|
-
selectionColor: rest.selectionColor,
|
|
145
|
-
selectionHandleColor: rest.selectionHandleColor,
|
|
146
|
-
autoComplete: rest.autoComplete,
|
|
147
|
-
textContentType: rest.textContentType,
|
|
148
|
-
showSoftInputOnFocus: rest.showSoftInputOnFocus,
|
|
149
|
-
underlineColorAndroid: rest.underlineColorAndroid,
|
|
150
|
-
}),
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
function handleChange(event: ISymbioteEvent): void {
|
|
154
|
-
// Event seam: the controlled handshake hinges on the change payload carrying `text`
|
|
155
|
-
// (+ `eventCount`). iOS and Android Fabric can key these differently, so log the actual
|
|
156
|
-
// shape here; a missing `text` means onValueChange never fires.
|
|
157
|
-
dlog(
|
|
158
|
-
`TextInput change keys=[${Object.keys(event.nativeEvent).join(',')}] ` +
|
|
159
|
-
`text=${JSON.stringify(event.nativeEvent.text)} count=${JSON.stringify(event.nativeEvent.eventCount)}`,
|
|
160
|
-
);
|
|
161
|
-
const changedText = textFromChange(event);
|
|
162
|
-
if (changedText !== undefined) {
|
|
163
|
-
// Record the text first, then bump the acknowledged count, so the count never runs ahead
|
|
164
|
-
// of the text it stands for.
|
|
165
|
-
lastNativeText = changedText;
|
|
166
|
-
rest.onValueChange?.(changedText, event);
|
|
167
|
-
// $bindable() sugar: with no onValueChange nothing could reject this report, so mirror it
|
|
168
|
-
// straight into the bound value. A caller that ALSO supplies onValueChange keeps full
|
|
169
|
-
// accept/reject control via the controlled-write effect below — see the module-script
|
|
170
|
-
// header for why echoing unconditionally would break that for it too.
|
|
171
|
-
if (rest.onValueChange === undefined) value = changedText;
|
|
172
|
-
}
|
|
173
|
-
const count = eventCountFromChange(event);
|
|
174
|
-
if (count !== undefined) mostRecentEventCount = count;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function handleFocus(event: ISymbioteEvent): void {
|
|
178
|
-
focused = true;
|
|
179
|
-
// Track focus app-wide so Keyboard.dismiss can blur this input without a ref.
|
|
180
|
-
const engineNode = hostShim?.engineNode;
|
|
181
|
-
if (engineNode !== undefined) setInputFocused(engineNode);
|
|
182
|
-
rest.onFocus?.(event);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function handleBlur(event: ISymbioteEvent): void {
|
|
186
|
-
focused = false;
|
|
187
|
-
const engineNode = hostShim?.engineNode;
|
|
188
|
-
if (engineNode !== undefined) setInputBlurred(engineNode);
|
|
189
|
-
rest.onBlur?.(event);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Controlled write: when JS-side `value` diverges from what native reported, command the new
|
|
193
|
-
// text down with the acknowledged count — a plain prop re-push would race the user's
|
|
194
|
-
// keystrokes; the command is the only stale-safe path. `value` and `count` are read
|
|
195
|
-
// UNCONDITIONALLY up front, before either early return: a rejecting parent never changes
|
|
196
|
-
// `props.value`, only `mostRecentEventCount` (bumped inside handleChange), so reading `count`
|
|
197
|
-
// only after the `shouldCommandText` guard would drop it from the tracked dependency set on the
|
|
198
|
-
// run that first falls through, and a later event-count-only change would then silently fail
|
|
199
|
-
// to retrigger this effect (same class of bug Switch's snap-back effect avoids). Verified
|
|
200
|
-
// against a real compiled mount (text-input.smoke.test.ts): the shim's insertOne() calls
|
|
201
|
-
// makeLive() synchronously as part of the SAME appendChild/insertBefore the compiler emits,
|
|
202
|
-
// before `bind:this` fires, so `hostShim.engineNode` is always populated by the time this
|
|
203
|
-
// first runs.
|
|
204
|
-
$effect(() => {
|
|
205
|
-
const engineNode = hostShim?.engineNode;
|
|
206
|
-
// `value` (component-scope, now $bindable()) IS the authoritative prop read — no local
|
|
207
|
-
// shadow needed; reading it here still establishes the effect's dependency exactly as the
|
|
208
|
-
// old `const value = rawProps.value` line did.
|
|
209
|
-
const currentValue = value;
|
|
210
|
-
const count = mostRecentEventCount;
|
|
211
|
-
if (engineNode === undefined) return;
|
|
212
|
-
if (!shouldCommandText(lastNativeText, currentValue)) return;
|
|
213
|
-
const selStart = rest.selection?.start ?? SELECTION_NONE;
|
|
214
|
-
const selEnd =
|
|
215
|
-
rest.selection?.end ?? rest.selection?.start ?? SELECTION_NONE;
|
|
216
|
-
dlog(
|
|
217
|
-
`TextInput setTextAndSelection count=${count} text=${JSON.stringify(currentValue)}`,
|
|
218
|
-
);
|
|
219
|
-
dispatchViewCommand(engineNode, 'setTextAndSelection', [
|
|
220
|
-
count,
|
|
221
|
-
currentValue,
|
|
222
|
-
selStart,
|
|
223
|
-
selEnd,
|
|
224
|
-
]);
|
|
225
|
-
lastNativeText = currentValue;
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// autoFocus is driven in JS, not as a native prop: once the host node first goes live, command
|
|
229
|
-
// `focus` down once (RN does the same via TextInputState.focusInput). No commit-timing retry
|
|
230
|
-
// needed here (unlike Vue's whenCommitted) — same synchronous makeLive() guarantee the
|
|
231
|
-
// controlled-write effect above relies on.
|
|
232
|
-
$effect(() => {
|
|
233
|
-
const engineNode = hostShim?.engineNode;
|
|
234
|
-
if (autoFocused || engineNode === undefined || rest.autoFocus !== true)
|
|
235
|
-
return;
|
|
236
|
-
autoFocused = true;
|
|
237
|
-
dlog('TextInput autoFocus -> focus command');
|
|
238
|
-
dispatchViewCommand(engineNode, 'focus', []);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
// The imperative API RN exposes on a TextInput ref, as plain instance-script functions — the
|
|
242
|
-
// Svelte 5 mechanism (no useImperativeHandle/expose() rune): a parent's `bind:this={ref}` gets
|
|
243
|
-
// `ref.focus()` etc. callable directly, typed via the re-exported `ITextInputHandle` above.
|
|
244
|
-
// focus/blur drive native view commands; clear/setSelection reuse setTextAndSelection (the
|
|
245
|
-
// same stale-safe path as a controlled write), echoing the acknowledged event count.
|
|
246
|
-
export function focus(): void {
|
|
247
|
-
const engineNode = hostShim?.engineNode;
|
|
248
|
-
if (engineNode !== undefined) dispatchViewCommand(engineNode, 'focus', []);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
export function blur(): void {
|
|
252
|
-
// Routes through TextInputState so the app-wide focus tracking clears too.
|
|
253
|
-
blurTextInput(hostShim?.engineNode ?? null);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export function clear(): void {
|
|
257
|
-
const engineNode = hostShim?.engineNode;
|
|
258
|
-
if (engineNode === undefined) return;
|
|
259
|
-
dispatchViewCommand(engineNode, 'setTextAndSelection', [
|
|
260
|
-
mostRecentEventCount,
|
|
261
|
-
'',
|
|
262
|
-
0,
|
|
263
|
-
0,
|
|
264
|
-
]);
|
|
265
|
-
lastNativeText = '';
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
export function isFocused(): boolean {
|
|
269
|
-
return focused;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
export function setSelection(start: number, end: number): void {
|
|
273
|
-
const engineNode = hostShim?.engineNode;
|
|
274
|
-
if (engineNode === undefined) return;
|
|
275
|
-
const current = lastNativeText ?? '';
|
|
276
|
-
dispatchViewCommand(engineNode, 'setTextAndSelection', [
|
|
277
|
-
mostRecentEventCount,
|
|
278
|
-
current,
|
|
279
|
-
start,
|
|
280
|
-
end,
|
|
281
|
-
]);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// The four host-ref methods, FORWARDED to the engine node rather than reimplemented.
|
|
285
|
-
//
|
|
286
|
-
// Without them a TextInput ref was poorer than any other host ref in the adapter: every other
|
|
287
|
-
// primitive hands back a node carrying measure/measureInWindow/measureLayout/setNativeProps, and
|
|
288
|
-
// this wrapper closed the node behind five methods of its own. The lowered path lost the opposite
|
|
289
|
-
// four, so the two paths were not "one narrowing the other" but two different surfaces.
|
|
290
|
-
// `ITextInputHandle` is the union of both halves, and `text-input.smoke.test.ts` derives what this
|
|
291
|
-
// component must expose from `buildTextInputHandle` — so a method added to core fails here rather
|
|
292
|
-
// than going missing on whichever path an app happened to take.
|
|
293
|
-
//
|
|
294
|
-
// Not typed as `satisfies ITextInputHandle`: these are instance-script exports, so the component's
|
|
295
|
-
// imperative surface is structurally invisible to the compiler — which is exactly why the test is
|
|
296
|
-
// the guard here and the type is the guard on React, Angular and Solid.
|
|
297
|
-
export function measure(callback: IMeasureOnSuccess): void {
|
|
298
|
-
hostShim?.engineNode?.measure(callback);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
export function measureInWindow(callback: IMeasureInWindowOnSuccess): void {
|
|
302
|
-
hostShim?.engineNode?.measureInWindow(callback);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
export function measureLayout(
|
|
306
|
-
relativeToNativeNode: ISymbioteNode | number,
|
|
307
|
-
onSuccess: IMeasureLayoutOnSuccess,
|
|
308
|
-
onFail?: () => void,
|
|
309
|
-
): void {
|
|
310
|
-
hostShim?.engineNode?.measureLayout(
|
|
311
|
-
relativeToNativeNode,
|
|
312
|
-
onSuccess,
|
|
313
|
-
onFail,
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export function setNativeProps(nativeProps: Record<string, unknown>): void {
|
|
318
|
-
hostShim?.engineNode?.setNativeProps(nativeProps);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const descriptor = $derived(
|
|
322
|
-
renderTextInput({
|
|
323
|
-
multiline: isMultiline,
|
|
324
|
-
text,
|
|
325
|
-
mostRecentEventCount,
|
|
326
|
-
selection: rest.selection,
|
|
327
|
-
folded,
|
|
328
|
-
passthrough: {
|
|
329
|
-
...forwardProps(rest),
|
|
330
|
-
onChange: handleChange,
|
|
331
|
-
onFocus: handleFocus,
|
|
332
|
-
onBlur: handleBlur,
|
|
333
|
-
},
|
|
334
|
-
}),
|
|
335
|
-
);
|
|
336
|
-
|
|
337
|
-
// renderTextInput()'s Descriptor has zero children (the whole surface is the `text`/
|
|
338
|
-
// `mostRecentEventCount` prop handshake) — wired anyway for the same uniform-shape reason
|
|
339
|
-
// Switch is, see its header comment.
|
|
340
|
-
const syncChildren = createDescriptorChildrenSync();
|
|
341
|
-
$effect(() => {
|
|
342
|
-
syncChildren(hostShim, descriptor.children);
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
// See View.svelte's note on `{@attach}`. `rest` (not `value`, which never carries a symbol
|
|
346
|
-
// key) is the same attachment-bearing bag `rawProps` used to be.
|
|
347
|
-
const syncAttachments = createAttachmentsSync();
|
|
348
|
-
$effect(() => {
|
|
349
|
-
syncAttachments(hostShim, rest);
|
|
350
|
-
});
|
|
351
|
-
</script>
|
|
352
|
-
|
|
353
|
-
<!--
|
|
354
|
-
The `-managed` tags, matching `renderTextInput`'s intrinsics. They resolve to the SAME native view
|
|
355
|
-
as the plain ones; the separate spelling is what keeps the host behavior off this node. The machine
|
|
356
|
-
is registered against `symbiote-text-input` / `…-multiline`, which only the LOWERING transform
|
|
357
|
-
emits, and this wrapper runs the same lifecycle itself (focus/blur mirror, event counter,
|
|
358
|
-
controlled write, autoFocus) — so sharing a tag would give one node two owners.
|
|
359
|
-
|
|
360
|
-
Written literally rather than as `<svelte:element this={descriptor.type}>` for the reason the
|
|
361
|
-
header records, which means this pair does NOT follow `render-text-input.ts` on its own: if those
|
|
362
|
-
intrinsics are renamed again, this file has to move with them. `text-input-tag.test.ts` is what
|
|
363
|
-
makes that a failing test instead of a silent double-owner.
|
|
364
|
-
-->
|
|
365
|
-
{#if isMultiline}
|
|
366
|
-
<symbiote-text-input-multiline-managed
|
|
367
|
-
p={descriptor.props}
|
|
368
|
-
bind:this={hostShim}
|
|
369
|
-
/>
|
|
370
|
-
{:else}
|
|
371
|
-
<symbiote-text-input-managed p={descriptor.props} bind:this={hostShim} />
|
|
372
|
-
{/if}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { ITextInputProps } from './text-input-props';
|
|
2
|
-
export type { ITextInputProps };
|
|
3
|
-
export type { ITextInputHandle } from '@symbiote-native/components';
|
|
4
|
-
import { type ISymbioteNode, type IMeasureOnSuccess, type IMeasureInWindowOnSuccess, type IMeasureLayoutOnSuccess } from '@symbiote-native/engine';
|
|
5
|
-
declare const Index: import("svelte").Component<ITextInputProps, {
|
|
6
|
-
focus: () => void;
|
|
7
|
-
blur: () => void;
|
|
8
|
-
clear: () => void;
|
|
9
|
-
isFocused: () => boolean;
|
|
10
|
-
setSelection: (start: number, end: number) => void;
|
|
11
|
-
measure: (callback: IMeasureOnSuccess) => void;
|
|
12
|
-
measureInWindow: (callback: IMeasureInWindowOnSuccess) => void;
|
|
13
|
-
measureLayout: (relativeToNativeNode: ISymbioteNode | number, onSuccess: IMeasureLayoutOnSuccess, onFail?: () => void) => void;
|
|
14
|
-
setNativeProps: (nativeProps: Record<string, unknown>) => void;
|
|
15
|
-
}, "value">;
|
|
16
|
-
type Index = ReturnType<typeof Index>;
|
|
17
|
-
export default Index;
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
// TouchableHighlight: built on Pressable. Ported against RN's own source
|
|
3
|
-
// (.vendors/react-native/.../Components/Touchable/TouchableHighlight.js) after the 2026-08-19
|
|
4
|
-
// audit; Solid migrated first, then React, Vue and Angular, and this is Svelte.
|
|
5
|
-
//
|
|
6
|
-
// RN drives the underlay from THREE Pressability callbacks, not from a `pressed` flag, and the
|
|
7
|
-
// difference is visible: onPress re-shows the underlay and holds it for delayPressOut, so a tap
|
|
8
|
-
// too fast to see still flashes. The previous port derived the highlight from Pressable's
|
|
9
|
-
// `pressed` through `highlightPressedStyle`, which cannot express that hold — the flag is
|
|
10
|
-
// already false by then. The machine itself
|
|
11
|
-
// (createHighlightUnderlayHandlers) is shared; Svelte owns the reactive `shown` cell and the
|
|
12
|
-
// real timers.
|
|
13
|
-
import type { ITouchableHighlightProps } from './touchable-highlight-props';
|
|
14
|
-
|
|
15
|
-
export type { ITouchableHighlightProps };
|
|
16
|
-
</script>
|
|
17
|
-
|
|
18
|
-
<script lang="ts">
|
|
19
|
-
import {
|
|
20
|
-
createHighlightUnderlayHandlers,
|
|
21
|
-
createHighlightUnderlayRuntime,
|
|
22
|
-
hasTouchablePressHandler,
|
|
23
|
-
resolveHighlightExtraStyles,
|
|
24
|
-
} from '@symbiote-native/components';
|
|
25
|
-
import type {
|
|
26
|
-
ISymbioteEvent,
|
|
27
|
-
IStyleProp,
|
|
28
|
-
IViewStyle,
|
|
29
|
-
} from '@symbiote-native/engine';
|
|
30
|
-
import Pressable from '../pressable/index.svelte';
|
|
31
|
-
|
|
32
|
-
function scheduleTimeout(callback: () => void, ms: number): () => void {
|
|
33
|
-
const id = setTimeout(callback, ms);
|
|
34
|
-
return () => clearTimeout(id);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// onPress/onPressIn/onPressOut are INTERCEPTED, not forwarded: RN drives the underlay from
|
|
38
|
-
// those three and then calls the caller's. delayPressOut is CONSUMED (it is the underlay hold),
|
|
39
|
-
// so it must not also reach the host as an unknown Fabric prop. onLongPress deliberately stays
|
|
40
|
-
// in `rest` — Pressable fires it directly — and is only READ here, for the has-handler gate.
|
|
41
|
-
let {
|
|
42
|
-
activeOpacity,
|
|
43
|
-
underlayColor,
|
|
44
|
-
style,
|
|
45
|
-
delayPressOut = 0,
|
|
46
|
-
onPress,
|
|
47
|
-
onPressIn,
|
|
48
|
-
onPressOut,
|
|
49
|
-
onShowUnderlay,
|
|
50
|
-
onHideUnderlay,
|
|
51
|
-
children: content,
|
|
52
|
-
...rest
|
|
53
|
-
}: ITouchableHighlightProps = $props();
|
|
54
|
-
|
|
55
|
-
// `shown` is NOT `pressed`: RN holds the underlay past the tap for delayPressOut.
|
|
56
|
-
let shown = $state(false);
|
|
57
|
-
// Setup-scope only, mutated by the machine on every event and never read reactively — same as
|
|
58
|
-
// Pressable's own runtime, never `$state`.
|
|
59
|
-
const runtime = createHighlightUnderlayRuntime();
|
|
60
|
-
|
|
61
|
-
// RN's _hasPressHandler gate: a TouchableHighlight with no press callback is decorative, and
|
|
62
|
-
// flashing an underlay under a touch passing through it is wrong.
|
|
63
|
-
const hasPressHandler = $derived(
|
|
64
|
-
hasTouchablePressHandler({
|
|
65
|
-
onPress,
|
|
66
|
-
onPressIn,
|
|
67
|
-
onPressOut,
|
|
68
|
-
onLongPress: rest.onLongPress,
|
|
69
|
-
}),
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
// Rebuilt whenever the config changes; the runtime persists across rebuilds so an in-flight
|
|
73
|
-
// hide timer survives one.
|
|
74
|
-
const underlay = $derived(
|
|
75
|
-
createHighlightUnderlayHandlers(
|
|
76
|
-
{ delayPressOut, hasPressHandler, schedule: scheduleTimeout },
|
|
77
|
-
runtime,
|
|
78
|
-
{
|
|
79
|
-
setShown(next: boolean): void {
|
|
80
|
-
shown = next;
|
|
81
|
-
},
|
|
82
|
-
onShowUnderlay: () => onShowUnderlay?.(),
|
|
83
|
-
onHideUnderlay: () => onHideUnderlay?.(),
|
|
84
|
-
},
|
|
85
|
-
),
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
const extra = $derived(
|
|
89
|
-
resolveHighlightExtraStyles({
|
|
90
|
-
shown,
|
|
91
|
-
hasPressHandler,
|
|
92
|
-
underlayColor,
|
|
93
|
-
activeOpacity,
|
|
94
|
-
}),
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
// ITEM 7 IS DELIBERATELY NOT FIXED HERE, and this is the whole of the divergence from RN. RN
|
|
98
|
-
// clones its single child with `extra.child`, so the lowered opacity lands on the CHILD while
|
|
99
|
-
// the container keeps an opaque underlay; React does that with cloneElement and Vue with
|
|
100
|
-
// cloneVNode. Svelte hands a component an opaque `Snippet` — a function that renders into an
|
|
101
|
-
// anchor. It cannot be introspected, cloned, or given a prop, and it may have several roots, so
|
|
102
|
-
// there is NO way to reach the child short of a permanent wrapper View. That wrapper would sit
|
|
103
|
-
// in the flex chain between this container and the children and silently re-parent any `flex`
|
|
104
|
-
// they declare; the fake Fabric runs no Yoga, so no headless test can measure the damage.
|
|
105
|
-
// Shipping an unmeasurable layout change is worse than the visual approximation, so both styles
|
|
106
|
-
// stay on the container (the child style folded in LAST), exactly as Solid and Angular decided.
|
|
107
|
-
// resolveHighlightExtraStyles keeps them separate regardless, so a future fix — which needs a
|
|
108
|
-
// real device, not a test — is unblocked.
|
|
109
|
-
const containerStyle = $derived<IStyleProp<IViewStyle>>(
|
|
110
|
-
extra === undefined ? style : [style, extra.underlay, extra.child],
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
// Visual first, then the caller's callback — RN's order in _createPressabilityConfig.
|
|
114
|
-
function handlePressIn(event: ISymbioteEvent): void {
|
|
115
|
-
underlay.handlePressIn(event);
|
|
116
|
-
onPressIn?.(event);
|
|
117
|
-
}
|
|
118
|
-
function handlePress(event: ISymbioteEvent): void {
|
|
119
|
-
underlay.handlePress(event);
|
|
120
|
-
onPress?.(event);
|
|
121
|
-
}
|
|
122
|
-
function handlePressOut(event: ISymbioteEvent): void {
|
|
123
|
-
underlay.handlePressOut(event);
|
|
124
|
-
onPressOut?.(event);
|
|
125
|
-
}
|
|
126
|
-
</script>
|
|
127
|
-
|
|
128
|
-
<Pressable
|
|
129
|
-
__minPressDuration={0}
|
|
130
|
-
{...rest}
|
|
131
|
-
style={containerStyle}
|
|
132
|
-
onPress={handlePress}
|
|
133
|
-
onPressIn={handlePressIn}
|
|
134
|
-
onPressOut={handlePressOut}
|
|
135
|
-
>
|
|
136
|
-
{#snippet children()}
|
|
137
|
-
{@render content?.()}
|
|
138
|
-
{/snippet}
|
|
139
|
-
</Pressable>
|
|
@@ -1,9 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,49 +0,0 @@
|
|
|
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 {
|
|
17
|
-
backgroundProps,
|
|
18
|
-
selectableBackground,
|
|
19
|
-
} from '@symbiote-native/components';
|
|
20
|
-
import { dlog } from '@symbiote-native/engine';
|
|
21
|
-
import Pressable from '../pressable/index.svelte';
|
|
22
|
-
|
|
23
|
-
let {
|
|
24
|
-
background,
|
|
25
|
-
useForeground = false,
|
|
26
|
-
children: content,
|
|
27
|
-
...rest
|
|
28
|
-
}: ITouchableNativeFeedbackProps = $props();
|
|
29
|
-
|
|
30
|
-
// RN defaults a missing background to SelectableBackground() so the touchable always shows
|
|
31
|
-
// feedback; mirror that here.
|
|
32
|
-
const resolved = $derived(background ?? selectableBackground());
|
|
33
|
-
|
|
34
|
-
$effect(() => {
|
|
35
|
-
dlog(
|
|
36
|
-
`TouchableNativeFeedback render ${resolved.type} useForeground ${useForeground}`,
|
|
37
|
-
);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const nativeProps = $derived(backgroundProps(resolved, useForeground));
|
|
41
|
-
</script>
|
|
42
|
-
|
|
43
|
-
<Pressable __minPressDuration={0} {...rest}>
|
|
44
|
-
{#snippet children()}
|
|
45
|
-
<symbiote-view p={nativeProps}>
|
|
46
|
-
{@render content?.()}
|
|
47
|
-
</symbiote-view>
|
|
48
|
-
{/snippet}
|
|
49
|
-
</Pressable>
|
|
@@ -1,5 +0,0 @@
|
|
|
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;
|