@symbiote-native/svelte 0.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +8 -10
  2. package/build/components/{activity-indicator/activity-indicator-props.d.ts → activity-indicator-props.d.ts} +1 -1
  3. package/build/components/flat-list/index.svelte +4 -4
  4. package/build/components/{image-background/image-background-props.d.ts → image-background-props.d.ts} +1 -1
  5. package/build/components/index.d.ts +6 -24
  6. package/build/components/index.js +0 -18
  7. package/build/components/keyboard-avoiding-view/index.svelte +6 -6
  8. package/build/components/modal/index.svelte +7 -7
  9. package/build/components/pressable/pressable-props.d.ts +1 -0
  10. package/build/components/refresh-control-props.d.ts +1 -0
  11. package/build/components/safe-area-view-props.d.ts +1 -0
  12. package/build/components/text-props.d.ts +1 -0
  13. package/build/components/virtualized-list/index.svelte +71 -250
  14. package/build/dom-shim/canonical-prop-names.d.ts +2 -0
  15. package/build/dom-shim/canonical-prop-names.js +320 -0
  16. package/build/dom-shim/document.js +1 -1
  17. package/build/dom-shim/element.d.ts +17 -5
  18. package/build/dom-shim/element.js +258 -40
  19. package/build/dom-shim/fold-host-bag.d.ts +1 -0
  20. package/build/dom-shim/fold-host-bag.js +7 -0
  21. package/build/dom-shim/patch-globals.js +35 -7
  22. package/build/dom-shim/shim-node.js +11 -0
  23. package/build/dom-shim/style-cache.d.ts +1 -0
  24. package/build/dom-shim/style-cache.js +27 -0
  25. package/build/dom-shim/text.js +2 -2
  26. package/build/host-instance.d.ts +2 -3
  27. package/build/host-instance.js +9 -4
  28. package/build/index.d.ts +8 -4
  29. package/build/index.js +21 -5
  30. package/build/intrinsic-elements.d.ts +63 -0
  31. package/build/modules/animated/index.d.ts +7 -27
  32. package/build/modules/animated/index.js +24 -34
  33. package/build/modules/image/index.d.ts +3 -0
  34. package/build/modules/image/index.js +15 -0
  35. package/build/preprocessor/scoped-styles.js +1 -1
  36. package/build/register.d.ts +1 -0
  37. package/build/register.js +15 -0
  38. package/build/root-element.js +1 -1
  39. package/metro-css-parser.cjs +2 -1
  40. package/metro-svelte-transformer.cjs +30 -12
  41. package/package.json +19 -9
  42. package/build/components/RefreshControl.svelte +0 -40
  43. package/build/components/RefreshControl.svelte.d.ts +0 -5
  44. package/build/components/SafeAreaView.svelte +0 -34
  45. package/build/components/SafeAreaView.svelte.d.ts +0 -5
  46. package/build/components/Text.svelte +0 -34
  47. package/build/components/Text.svelte.d.ts +0 -5
  48. package/build/components/View.svelte +0 -43
  49. package/build/components/View.svelte.d.ts +0 -5
  50. package/build/components/activity-indicator/activity-indicator-platform.android.d.ts +0 -3
  51. package/build/components/activity-indicator/activity-indicator-platform.android.js +0 -5
  52. package/build/components/activity-indicator/activity-indicator-platform.d.ts +0 -1
  53. package/build/components/activity-indicator/activity-indicator-platform.ios.d.ts +0 -3
  54. package/build/components/activity-indicator/activity-indicator-platform.ios.js +0 -6
  55. package/build/components/activity-indicator/activity-indicator-platform.js +0 -5
  56. package/build/components/activity-indicator/index.svelte +0 -63
  57. package/build/components/activity-indicator/index.svelte.d.ts +0 -5
  58. package/build/components/button.svelte +0 -76
  59. package/build/components/button.svelte.d.ts +0 -5
  60. package/build/components/image/image-logic.d.ts +0 -2
  61. package/build/components/image/image-logic.js +0 -135
  62. package/build/components/image/index.d.ts +0 -3
  63. package/build/components/image/index.js +0 -10
  64. package/build/components/image/index.svelte +0 -66
  65. package/build/components/image/index.svelte.d.ts +0 -5
  66. package/build/components/image-background/index.svelte +0 -87
  67. package/build/components/image-background/index.svelte.d.ts +0 -5
  68. package/build/components/input-accessory-view/index.svelte +0 -51
  69. package/build/components/input-accessory-view/index.svelte.d.ts +0 -5
  70. package/build/components/pressable/index.svelte +0 -167
  71. package/build/components/pressable/index.svelte.d.ts +0 -5
  72. package/build/components/scroll-view/index.svelte +0 -331
  73. package/build/components/scroll-view/index.svelte.d.ts +0 -18
  74. package/build/components/scroll-view/scroll-view-platform-types.d.ts +0 -3
  75. package/build/components/scroll-view/scroll-view-platform.android.d.ts +0 -3
  76. package/build/components/scroll-view/scroll-view-platform.android.js +0 -3
  77. package/build/components/scroll-view/scroll-view-platform.d.ts +0 -1
  78. package/build/components/scroll-view/scroll-view-platform.ios.d.ts +0 -3
  79. package/build/components/scroll-view/scroll-view-platform.ios.js +0 -3
  80. package/build/components/scroll-view/scroll-view-platform.js +0 -4
  81. package/build/components/scroll-view/scroll-view-sticky-context.d.ts +0 -7
  82. package/build/components/scroll-view/scroll-view-sticky-context.js +0 -1
  83. package/build/components/scroll-view/sticky-header-props.d.ts +0 -10
  84. package/build/components/scroll-view/sticky-header-props.js +0 -1
  85. package/build/components/scroll-view/sticky-header.svelte +0 -287
  86. package/build/components/scroll-view/sticky-header.svelte.d.ts +0 -5
  87. package/build/components/switch/index.svelte +0 -134
  88. package/build/components/switch/index.svelte.d.ts +0 -5
  89. package/build/components/text-input/index.svelte +0 -316
  90. package/build/components/text-input/index.svelte.d.ts +0 -12
  91. package/build/components/touchable-highlight/index.svelte +0 -138
  92. package/build/components/touchable-highlight/index.svelte.d.ts +0 -5
  93. package/build/components/touchable-native-feedback/index.d.ts +0 -9
  94. package/build/components/touchable-native-feedback/index.js +0 -14
  95. package/build/components/touchable-native-feedback/touchable-native-feedback.svelte +0 -49
  96. package/build/components/touchable-native-feedback/touchable-native-feedback.svelte.d.ts +0 -5
  97. package/build/components/touchable-opacity/index.svelte +0 -180
  98. package/build/components/touchable-opacity/index.svelte.d.ts +0 -5
  99. package/build/components/touchable-without-feedback/index.svelte +0 -74
  100. package/build/components/touchable-without-feedback/index.svelte.d.ts +0 -5
  101. package/build/modules/animated/animated-component-props.d.ts +0 -7
  102. package/build/modules/animated/animated-component-props.js +0 -1
  103. package/build/modules/animated/create-animated-component.d.ts +0 -5
  104. package/build/modules/animated/create-animated-component.js +0 -131
  105. /package/build/components/{activity-indicator/activity-indicator-props.js → activity-indicator-props.js} +0 -0
  106. /package/build/components/{image-background/image-background-props.js → image-background-props.js} +0 -0
  107. /package/build/{components/scroll-view/scroll-view-platform-types.js → intrinsic-elements.js} +0 -0
@@ -1,316 +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
- } 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(
93
- source: Omit<ITextInputProps, 'value'>,
94
- ): Record<string, unknown> {
95
- const resolved = resolveAccessibilityProps(source);
96
- const result: Record<string, unknown> = {};
97
- for (const [key, propValue] of Object.entries(resolved)) {
98
- if (!HANDLED_KEYS.includes(key)) result[key] = propValue;
99
- }
100
- return result;
101
- }
102
-
103
- // `value` is $bindable() (see the module-script header for the full reasoning); everything
104
- // else keeps flowing through `rest`, the exact twin of the old whole-object `rawProps` for
105
- // every non-`value` field.
106
- let { value = $bindable(), ...rest }: ITextInputProps = $props();
107
-
108
- // $state.raw, NOT $state: identity concern, see the module-script header above.
109
- let hostShim = $state.raw<ShimElement | null>(null);
110
-
111
- // The count native last acknowledged. $state so the bag echoes it back on every commit and
112
- // the exposed handle reads the latest (the Svelte twin of React's useState / Vue's ref).
113
- let mostRecentEventCount = $state(INITIAL_EVENT_COUNT);
114
- // The last text native holds, as far as JS knows. Seeded from the mount-time value — the
115
- // `text` prop already carries it down via the bag, so the FIRST controlled value is not a
116
- // divergence and must NOT re-command. Bookkeeping, not render state: a plain `let`, same as
117
- // Vue's setup-scope `let lastNativeText`.
118
- let lastNativeText = foldText(value, rest.defaultValue);
119
- // JS-side focus state, mirrored from the focus/blur events for isFocused(): native exposes no
120
- // synchronous focus getter (RN's TextInputState keeps the same).
121
- let focused = false;
122
- // autoFocus fires once, when the host node first goes live; guarded so a later re-render (or
123
- // an intrinsic swap between single/multiline) doesn't re-focus.
124
- let autoFocused = false;
125
-
126
- const isMultiline = $derived(rest.multiline === true);
127
- const text = $derived(foldText(value, rest.defaultValue));
128
- const folded = $derived(
129
- resolveTextInputProps({
130
- inputMode: rest.inputMode,
131
- keyboardType: rest.keyboardType,
132
- enterKeyHint: rest.enterKeyHint,
133
- returnKeyType: rest.returnKeyType,
134
- readOnly: rest.readOnly,
135
- editable: rest.editable,
136
- submitBehavior: rest.submitBehavior,
137
- blurOnSubmit: rest.blurOnSubmit,
138
- multiline: isMultiline,
139
- cursorColor: rest.cursorColor,
140
- selectionColor: rest.selectionColor,
141
- selectionHandleColor: rest.selectionHandleColor,
142
- autoComplete: rest.autoComplete,
143
- textContentType: rest.textContentType,
144
- showSoftInputOnFocus: rest.showSoftInputOnFocus,
145
- underlineColorAndroid: rest.underlineColorAndroid,
146
- }),
147
- );
148
-
149
- function handleChange(event: ISymbioteEvent): void {
150
- // Event seam: the controlled handshake hinges on the change payload carrying `text`
151
- // (+ `eventCount`). iOS and Android Fabric can key these differently, so log the actual
152
- // shape here; a missing `text` means onValueChange never fires.
153
- dlog(
154
- `TextInput change keys=[${Object.keys(event.nativeEvent).join(',')}] ` +
155
- `text=${JSON.stringify(event.nativeEvent.text)} count=${JSON.stringify(event.nativeEvent.eventCount)}`,
156
- );
157
- const changedText = textFromChange(event);
158
- if (changedText !== undefined) {
159
- // Record the text first, then bump the acknowledged count, so the count never runs ahead
160
- // of the text it stands for.
161
- lastNativeText = changedText;
162
- rest.onValueChange?.(changedText, event);
163
- // $bindable() sugar: with no onValueChange nothing could reject this report, so mirror it
164
- // straight into the bound value. A caller that ALSO supplies onValueChange keeps full
165
- // accept/reject control via the controlled-write effect below — see the module-script
166
- // header for why echoing unconditionally would break that for it too.
167
- if (rest.onValueChange === undefined) value = changedText;
168
- }
169
- const count = eventCountFromChange(event);
170
- if (count !== undefined) mostRecentEventCount = count;
171
- }
172
-
173
- function handleFocus(event: ISymbioteEvent): void {
174
- focused = true;
175
- // Track focus app-wide so Keyboard.dismiss can blur this input without a ref.
176
- const engineNode = hostShim?.engineNode;
177
- if (engineNode !== undefined) setInputFocused(engineNode);
178
- rest.onFocus?.(event);
179
- }
180
-
181
- function handleBlur(event: ISymbioteEvent): void {
182
- focused = false;
183
- const engineNode = hostShim?.engineNode;
184
- if (engineNode !== undefined) setInputBlurred(engineNode);
185
- rest.onBlur?.(event);
186
- }
187
-
188
- // Controlled write: when JS-side `value` diverges from what native reported, command the new
189
- // text down with the acknowledged count — a plain prop re-push would race the user's
190
- // keystrokes; the command is the only stale-safe path. `value` and `count` are read
191
- // UNCONDITIONALLY up front, before either early return: a rejecting parent never changes
192
- // `props.value`, only `mostRecentEventCount` (bumped inside handleChange), so reading `count`
193
- // only after the `shouldCommandText` guard would drop it from the tracked dependency set on the
194
- // run that first falls through, and a later event-count-only change would then silently fail
195
- // to retrigger this effect (same class of bug Switch's snap-back effect avoids). Verified
196
- // against a real compiled mount (text-input.smoke.test.ts): the shim's insertOne() calls
197
- // makeLive() synchronously as part of the SAME appendChild/insertBefore the compiler emits,
198
- // before `bind:this` fires, so `hostShim.engineNode` is always populated by the time this
199
- // first runs.
200
- $effect(() => {
201
- const engineNode = hostShim?.engineNode;
202
- // `value` (component-scope, now $bindable()) IS the authoritative prop read — no local
203
- // shadow needed; reading it here still establishes the effect's dependency exactly as the
204
- // old `const value = rawProps.value` line did.
205
- const currentValue = value;
206
- const count = mostRecentEventCount;
207
- if (engineNode === undefined) return;
208
- if (!shouldCommandText(lastNativeText, currentValue)) return;
209
- const selStart = rest.selection?.start ?? SELECTION_NONE;
210
- const selEnd =
211
- rest.selection?.end ?? rest.selection?.start ?? SELECTION_NONE;
212
- dlog(
213
- `TextInput setTextAndSelection count=${count} text=${JSON.stringify(currentValue)}`,
214
- );
215
- dispatchViewCommand(engineNode, 'setTextAndSelection', [
216
- count,
217
- currentValue,
218
- selStart,
219
- selEnd,
220
- ]);
221
- lastNativeText = currentValue;
222
- });
223
-
224
- // autoFocus is driven in JS, not as a native prop: once the host node first goes live, command
225
- // `focus` down once (RN does the same via TextInputState.focusInput). No commit-timing retry
226
- // needed here (unlike Vue's whenCommitted) — same synchronous makeLive() guarantee the
227
- // controlled-write effect above relies on.
228
- $effect(() => {
229
- const engineNode = hostShim?.engineNode;
230
- if (autoFocused || engineNode === undefined || rest.autoFocus !== true)
231
- return;
232
- autoFocused = true;
233
- dlog('TextInput autoFocus -> focus command');
234
- dispatchViewCommand(engineNode, 'focus', []);
235
- });
236
-
237
- // The imperative API RN exposes on a TextInput ref, as plain instance-script functions — the
238
- // Svelte 5 mechanism (no useImperativeHandle/expose() rune): a parent's `bind:this={ref}` gets
239
- // `ref.focus()` etc. callable directly, typed via the re-exported `ITextInputHandle` above.
240
- // focus/blur drive native view commands; clear/setSelection reuse setTextAndSelection (the
241
- // same stale-safe path as a controlled write), echoing the acknowledged event count.
242
- export function focus(): void {
243
- const engineNode = hostShim?.engineNode;
244
- if (engineNode !== undefined) dispatchViewCommand(engineNode, 'focus', []);
245
- }
246
-
247
- export function blur(): void {
248
- // Routes through TextInputState so the app-wide focus tracking clears too.
249
- blurTextInput(hostShim?.engineNode ?? null);
250
- }
251
-
252
- export function clear(): void {
253
- const engineNode = hostShim?.engineNode;
254
- if (engineNode === undefined) return;
255
- dispatchViewCommand(engineNode, 'setTextAndSelection', [
256
- mostRecentEventCount,
257
- '',
258
- 0,
259
- 0,
260
- ]);
261
- lastNativeText = '';
262
- }
263
-
264
- export function isFocused(): boolean {
265
- return focused;
266
- }
267
-
268
- export function setSelection(start: number, end: number): void {
269
- const engineNode = hostShim?.engineNode;
270
- if (engineNode === undefined) return;
271
- const current = lastNativeText ?? '';
272
- dispatchViewCommand(engineNode, 'setTextAndSelection', [
273
- mostRecentEventCount,
274
- current,
275
- start,
276
- end,
277
- ]);
278
- }
279
-
280
- const descriptor = $derived(
281
- renderTextInput({
282
- multiline: isMultiline,
283
- text,
284
- mostRecentEventCount,
285
- selection: rest.selection,
286
- folded,
287
- passthrough: {
288
- ...forwardProps(rest),
289
- onChange: handleChange,
290
- onFocus: handleFocus,
291
- onBlur: handleBlur,
292
- },
293
- }),
294
- );
295
-
296
- // renderTextInput()'s Descriptor has zero children (the whole surface is the `text`/
297
- // `mostRecentEventCount` prop handshake) — wired anyway for the same uniform-shape reason
298
- // Switch is, see its header comment.
299
- const syncChildren = createDescriptorChildrenSync();
300
- $effect(() => {
301
- syncChildren(hostShim, descriptor.children);
302
- });
303
-
304
- // See View.svelte's note on `{@attach}`. `rest` (not `value`, which never carries a symbol
305
- // key) is the same attachment-bearing bag `rawProps` used to be.
306
- const syncAttachments = createAttachmentsSync();
307
- $effect(() => {
308
- syncAttachments(hostShim, rest);
309
- });
310
- </script>
311
-
312
- {#if isMultiline}
313
- <symbiote-text-input-multiline p={descriptor.props} bind:this={hostShim} />
314
- {:else}
315
- <symbiote-text-input p={descriptor.props} bind:this={hostShim} />
316
- {/if}
@@ -1,12 +0,0 @@
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;
@@ -1,138 +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
- {...rest}
130
- style={containerStyle}
131
- onPress={handlePress}
132
- onPressIn={handlePressIn}
133
- onPressOut={handlePressOut}
134
- >
135
- {#snippet children()}
136
- {@render content?.()}
137
- {/snippet}
138
- </Pressable>
@@ -1,5 +0,0 @@
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;
@@ -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 {...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;