@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.
- package/LICENSE +21 -0
- package/README.md +161 -0
- package/build/bootstrap.d.ts +7 -0
- package/build/bootstrap.js +17 -0
- package/build/class-value.d.ts +6 -0
- package/build/class-value.js +81 -0
- package/build/components/RefreshControl.svelte +38 -0
- package/build/components/RefreshControl.svelte.d.ts +5 -0
- package/build/components/SafeAreaView.svelte +34 -0
- package/build/components/SafeAreaView.svelte.d.ts +5 -0
- package/build/components/Text.svelte +25 -0
- package/build/components/Text.svelte.d.ts +5 -0
- package/build/components/View.svelte +41 -0
- package/build/components/View.svelte.d.ts +5 -0
- package/build/components/activity-indicator/activity-indicator-platform.android.d.ts +3 -0
- package/build/components/activity-indicator/activity-indicator-platform.android.js +5 -0
- package/build/components/activity-indicator/activity-indicator-platform.d.ts +1 -0
- package/build/components/activity-indicator/activity-indicator-platform.ios.d.ts +3 -0
- package/build/components/activity-indicator/activity-indicator-platform.ios.js +6 -0
- package/build/components/activity-indicator/activity-indicator-platform.js +5 -0
- package/build/components/activity-indicator/activity-indicator-props.d.ts +5 -0
- package/build/components/activity-indicator/activity-indicator-props.js +1 -0
- package/build/components/activity-indicator/index.svelte +57 -0
- package/build/components/activity-indicator/index.svelte.d.ts +5 -0
- package/build/components/button-props.d.ts +5 -0
- package/build/components/button-props.js +1 -0
- package/build/components/button.svelte +71 -0
- package/build/components/button.svelte.d.ts +5 -0
- package/build/components/flat-list/flat-list-props.d.ts +10 -0
- package/build/components/flat-list/flat-list-props.js +1 -0
- package/build/components/flat-list/index.svelte +254 -0
- package/build/components/flat-list/index.svelte.d.ts +79 -0
- package/build/components/image/image-logic.d.ts +2 -0
- package/build/components/image/image-logic.js +125 -0
- package/build/components/image/image-props.d.ts +5 -0
- package/build/components/image/image-props.js +1 -0
- package/build/components/image/index.d.ts +3 -0
- package/build/components/image/index.js +10 -0
- package/build/components/image/index.svelte +66 -0
- package/build/components/image/index.svelte.d.ts +5 -0
- package/build/components/image-background/image-background-props.d.ts +10 -0
- package/build/components/image-background/image-background-props.js +1 -0
- package/build/components/image-background/index.svelte +81 -0
- package/build/components/image-background/index.svelte.d.ts +5 -0
- package/build/components/index.d.ts +50 -0
- package/build/components/index.js +24 -0
- package/build/components/input-accessory-view/index.svelte +48 -0
- package/build/components/input-accessory-view/index.svelte.d.ts +5 -0
- package/build/components/input-accessory-view/input-accessory-view-props.d.ts +11 -0
- package/build/components/input-accessory-view/input-accessory-view-props.js +1 -0
- package/build/components/keyboard-avoiding-view/index.svelte +135 -0
- package/build/components/keyboard-avoiding-view/index.svelte.d.ts +5 -0
- package/build/components/keyboard-avoiding-view/keyboard-avoiding-view-props.d.ts +15 -0
- package/build/components/keyboard-avoiding-view/keyboard-avoiding-view-props.js +1 -0
- package/build/components/modal/index.svelte +126 -0
- package/build/components/modal/index.svelte.d.ts +5 -0
- package/build/components/modal/modal-props.d.ts +24 -0
- package/build/components/modal/modal-props.js +1 -0
- package/build/components/pressable/index.svelte +160 -0
- package/build/components/pressable/index.svelte.d.ts +5 -0
- package/build/components/pressable/pressable-props.d.ts +27 -0
- package/build/components/pressable/pressable-props.js +1 -0
- package/build/components/refresh-control-props.d.ts +19 -0
- package/build/components/refresh-control-props.js +1 -0
- package/build/components/safe-area-view-props.d.ts +10 -0
- package/build/components/safe-area-view-props.js +1 -0
- package/build/components/scroll-view/index.svelte +298 -0
- package/build/components/scroll-view/index.svelte.d.ts +18 -0
- package/build/components/scroll-view/scroll-view-platform-types.d.ts +3 -0
- package/build/components/scroll-view/scroll-view-platform-types.js +1 -0
- package/build/components/scroll-view/scroll-view-platform.android.d.ts +3 -0
- package/build/components/scroll-view/scroll-view-platform.android.js +3 -0
- package/build/components/scroll-view/scroll-view-platform.d.ts +1 -0
- package/build/components/scroll-view/scroll-view-platform.ios.d.ts +3 -0
- package/build/components/scroll-view/scroll-view-platform.ios.js +3 -0
- package/build/components/scroll-view/scroll-view-platform.js +4 -0
- package/build/components/scroll-view/scroll-view-props.d.ts +78 -0
- package/build/components/scroll-view/scroll-view-props.js +1 -0
- package/build/components/scroll-view/scroll-view-sticky-context.d.ts +7 -0
- package/build/components/scroll-view/scroll-view-sticky-context.js +1 -0
- package/build/components/scroll-view/sticky-header-props.d.ts +10 -0
- package/build/components/scroll-view/sticky-header-props.js +1 -0
- package/build/components/scroll-view/sticky-header.svelte +254 -0
- package/build/components/scroll-view/sticky-header.svelte.d.ts +5 -0
- package/build/components/section-list/index.svelte +107 -0
- package/build/components/section-list/index.svelte.d.ts +57 -0
- package/build/components/section-list/section-list-props.d.ts +1 -0
- package/build/components/section-list/section-list-props.js +1 -0
- package/build/components/switch/index.svelte +123 -0
- package/build/components/switch/index.svelte.d.ts +5 -0
- package/build/components/switch/switch-platform-types.d.ts +5 -0
- package/build/components/switch/switch-platform-types.js +1 -0
- package/build/components/switch/switch-platform.android.d.ts +3 -0
- package/build/components/switch/switch-platform.android.js +8 -0
- package/build/components/switch/switch-platform.d.ts +1 -0
- package/build/components/switch/switch-platform.ios.d.ts +3 -0
- package/build/components/switch/switch-platform.ios.js +7 -0
- package/build/components/switch/switch-platform.js +5 -0
- package/build/components/switch/switch-props.d.ts +5 -0
- package/build/components/switch/switch-props.js +1 -0
- package/build/components/text-input/index.svelte +300 -0
- package/build/components/text-input/index.svelte.d.ts +12 -0
- package/build/components/text-input/text-input-props.d.ts +5 -0
- package/build/components/text-input/text-input-props.js +1 -0
- package/build/components/text-props.d.ts +23 -0
- package/build/components/text-props.js +1 -0
- package/build/components/touchable-base-props.d.ts +10 -0
- package/build/components/touchable-base-props.js +1 -0
- package/build/components/touchable-highlight/index.svelte +39 -0
- package/build/components/touchable-highlight/index.svelte.d.ts +5 -0
- package/build/components/touchable-highlight/touchable-highlight-props.d.ts +5 -0
- package/build/components/touchable-highlight/touchable-highlight-props.js +1 -0
- package/build/components/touchable-native-feedback/index.d.ts +9 -0
- package/build/components/touchable-native-feedback/index.js +14 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback-props.d.ts +8 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback-props.js +1 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback.svelte +44 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback.svelte.d.ts +5 -0
- package/build/components/touchable-opacity/index.svelte +124 -0
- package/build/components/touchable-opacity/index.svelte.d.ts +5 -0
- package/build/components/touchable-opacity/touchable-opacity-props.d.ts +5 -0
- package/build/components/touchable-opacity/touchable-opacity-props.js +1 -0
- package/build/components/touchable-without-feedback/index.svelte +19 -0
- package/build/components/touchable-without-feedback/index.svelte.d.ts +5 -0
- package/build/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +2 -0
- package/build/components/touchable-without-feedback/touchable-without-feedback-props.js +1 -0
- package/build/components/view-props.d.ts +29 -0
- package/build/components/view-props.js +1 -0
- package/build/components/virtualized-list/index.svelte +702 -0
- package/build/components/virtualized-list/index.svelte.d.ts +79 -0
- package/build/components/virtualized-list/virtualized-list-props.d.ts +70 -0
- package/build/components/virtualized-list/virtualized-list-props.js +73 -0
- package/build/components/virtualized-section-list/index.svelte +206 -0
- package/build/components/virtualized-section-list/index.svelte.d.ts +57 -0
- package/build/components/virtualized-section-list/virtualized-section-list-props.d.ts +62 -0
- package/build/components/virtualized-section-list/virtualized-section-list-props.js +1 -0
- package/build/create-tunnel/index.d.ts +3 -0
- package/build/create-tunnel/index.js +3 -0
- package/build/create-tunnel/tunnel-in.svelte +24 -0
- package/build/create-tunnel/tunnel-in.svelte.d.ts +9 -0
- package/build/create-tunnel/tunnel-out.svelte +14 -0
- package/build/create-tunnel/tunnel-out.svelte.d.ts +7 -0
- package/build/create-tunnel/tunnel.d.ts +9 -0
- package/build/create-tunnel/tunnel.js +9 -0
- package/build/descriptor-to-svelte.d.ts +7 -0
- package/build/descriptor-to-svelte.js +104 -0
- package/build/dom-shim/comment.d.ts +9 -0
- package/build/dom-shim/comment.js +24 -0
- package/build/dom-shim/document-fragment.d.ts +8 -0
- package/build/dom-shim/document-fragment.js +23 -0
- package/build/dom-shim/document.d.ts +23 -0
- package/build/dom-shim/document.js +61 -0
- package/build/dom-shim/element.d.ts +22 -0
- package/build/dom-shim/element.js +130 -0
- package/build/dom-shim/index.d.ts +5 -0
- package/build/dom-shim/index.js +7 -0
- package/build/dom-shim/patch-globals.d.ts +2 -0
- package/build/dom-shim/patch-globals.js +94 -0
- package/build/dom-shim/shim-node.d.ts +30 -0
- package/build/dom-shim/shim-node.js +207 -0
- package/build/dom-shim/text.d.ts +14 -0
- package/build/dom-shim/text.js +97 -0
- package/build/host-instance.d.ts +5 -0
- package/build/host-instance.js +48 -0
- package/build/index.d.ts +28 -0
- package/build/index.js +77 -0
- package/build/modules/accessibility-info/index.android.d.ts +2 -0
- package/build/modules/accessibility-info/index.android.js +4 -0
- package/build/modules/accessibility-info/index.d.ts +1 -0
- package/build/modules/accessibility-info/index.ios.d.ts +2 -0
- package/build/modules/accessibility-info/index.ios.js +3 -0
- package/build/modules/accessibility-info/index.js +5 -0
- package/build/modules/animated/AnimatedImage.svelte +115 -0
- package/build/modules/animated/AnimatedImage.svelte.d.ts +5 -0
- package/build/modules/animated/AnimatedScrollView.svelte +92 -0
- package/build/modules/animated/AnimatedScrollView.svelte.d.ts +17 -0
- package/build/modules/animated/AnimatedText.svelte +58 -0
- package/build/modules/animated/AnimatedText.svelte.d.ts +5 -0
- package/build/modules/animated/AnimatedView.svelte +95 -0
- package/build/modules/animated/AnimatedView.svelte.d.ts +5 -0
- package/build/modules/animated/animated-component-props.d.ts +7 -0
- package/build/modules/animated/animated-component-props.js +1 -0
- package/build/modules/animated/index.d.ts +58 -0
- package/build/modules/animated/index.js +69 -0
- package/build/modules/app-registry/index.d.ts +7 -0
- package/build/modules/app-registry/index.js +28 -0
- package/build/modules/status-bar/index.d.ts +5 -0
- package/build/modules/status-bar/index.js +21 -0
- package/build/modules/status-bar/index.svelte +20 -0
- package/build/modules/status-bar/index.svelte.d.ts +4 -0
- package/build/native-view-bridge.d.ts +4 -0
- package/build/native-view-bridge.js +27 -0
- package/build/preprocessor/forbid-web-only-constructs.d.ts +8 -0
- package/build/preprocessor/forbid-web-only-constructs.js +204 -0
- package/build/preprocessor/scoped-styles.d.ts +10 -0
- package/build/preprocessor/scoped-styles.js +399 -0
- package/build/render.d.ts +7 -0
- package/build/render.js +46 -0
- package/build/root-element.d.ts +3 -0
- package/build/root-element.js +27 -0
- package/build/runes/attachments.d.ts +5 -0
- package/build/runes/attachments.js +73 -0
- package/build/runes/dimensions-value.d.ts +4 -0
- package/build/runes/dimensions-value.js +30 -0
- package/build/runes/media-query.d.ts +8 -0
- package/build/runes/media-query.js +38 -0
- package/build/runes/use-color-scheme.svelte.d.ts +4 -0
- package/build/runes/use-color-scheme.svelte.js +25 -0
- package/build/runes/use-window-dimensions.svelte.d.ts +4 -0
- package/build/runes/use-window-dimensions.svelte.js +43 -0
- package/build/runes/window.d.ts +6 -0
- package/build/runes/window.js +28 -0
- package/build/scope-token.d.ts +2 -0
- package/build/scope-token.js +33 -0
- package/build/style-scope.d.ts +1 -0
- package/build/style-scope.js +29 -0
- package/metro-css-parser.cjs +9 -0
- package/metro-svelte-transformer.cjs +156 -0
- 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 @@
|
|
|
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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 @@
|
|
|
1
|
+
export {};
|