@symbiote-native/angular 0.7.0 → 1.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 +3 -3
- package/babel-lower-host-primitives.cjs +636 -0
- package/babel-register-composed.cjs +21 -3
- package/build/angular/anchor-host-registry.js +3 -1
- package/build/angular/anchor-host-registry.js.map +1 -1
- package/build/angular/bootstrap.js +1 -1
- package/build/angular/bootstrap.js.map +1 -1
- package/build/angular/components/activity-indicator/index.android.js +7 -2
- package/build/angular/components/activity-indicator/index.android.js.map +1 -1
- package/build/angular/components/activity-indicator/index.ios.js +3 -1
- package/build/angular/components/activity-indicator/index.ios.js.map +1 -1
- package/build/angular/components/activity-indicator/shared.d.ts +1 -1
- package/build/angular/components/activity-indicator/shared.js +1 -1
- package/build/angular/components/activity-indicator/shared.js.map +1 -1
- package/build/angular/components/button.d.ts +3 -0
- package/build/angular/components/button.js +22 -5
- package/build/angular/components/button.js.map +1 -1
- package/build/angular/components/flat-list/index.d.ts +1 -1
- package/build/angular/components/flat-list/index.js +47 -14
- package/build/angular/components/flat-list/index.js.map +1 -1
- package/build/angular/components/image/index.android.js +4 -10
- package/build/angular/components/image/index.android.js.map +1 -1
- package/build/angular/components/image/index.ios.js +4 -10
- package/build/angular/components/image/index.ios.js.map +1 -1
- package/build/angular/components/image/shared.d.ts +4 -4
- package/build/angular/components/image/shared.js +51 -33
- package/build/angular/components/image/shared.js.map +1 -1
- package/build/angular/components/image-background.d.ts +3 -0
- package/build/angular/components/image-background.js +32 -9
- package/build/angular/components/image-background.js.map +1 -1
- package/build/angular/components/input-accessory-view/index.d.ts +2 -1
- package/build/angular/components/input-accessory-view/index.js +18 -16
- package/build/angular/components/input-accessory-view/index.js.map +1 -1
- package/build/angular/components/keyboard-avoiding-view/index.d.ts +3 -1
- package/build/angular/components/keyboard-avoiding-view/index.js +56 -19
- package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
- package/build/angular/components/modal/index.d.ts +1 -0
- package/build/angular/components/modal/index.js +21 -10
- package/build/angular/components/modal/index.js.map +1 -1
- package/build/angular/components/pressable/index.d.ts +10 -5
- package/build/angular/components/pressable/index.js +61 -16
- package/build/angular/components/pressable/index.js.map +1 -1
- package/build/angular/components/refresh-control/index.d.ts +2 -1
- package/build/angular/components/refresh-control/index.js +13 -9
- package/build/angular/components/refresh-control/index.js.map +1 -1
- package/build/angular/components/safe-area-view/index.d.ts +3 -1
- package/build/angular/components/safe-area-view/index.js +22 -20
- package/build/angular/components/safe-area-view/index.js.map +1 -1
- package/build/angular/components/scroll-view/index.android.js +44 -14
- package/build/angular/components/scroll-view/index.android.js.map +1 -1
- package/build/angular/components/scroll-view/index.ios.js +13 -5
- package/build/angular/components/scroll-view/index.ios.js.map +1 -1
- package/build/angular/components/scroll-view/projection.d.ts +32 -0
- package/build/angular/components/scroll-view/projection.js +212 -47
- package/build/angular/components/scroll-view/projection.js.map +1 -1
- package/build/angular/components/scroll-view/shared.d.ts +2 -0
- package/build/angular/components/scroll-view/shared.js +48 -11
- package/build/angular/components/scroll-view/shared.js.map +1 -1
- package/build/angular/components/scroll-view/sticky-header.js +7 -3
- package/build/angular/components/scroll-view/sticky-header.js.map +1 -1
- package/build/angular/components/section-list/index.d.ts +9 -1
- package/build/angular/components/section-list/index.js +72 -18
- package/build/angular/components/section-list/index.js.map +1 -1
- package/build/angular/components/switch/index.android.js +23 -9
- package/build/angular/components/switch/index.android.js.map +1 -1
- package/build/angular/components/switch/index.ios.js +23 -9
- package/build/angular/components/switch/index.ios.js.map +1 -1
- package/build/angular/components/switch/shared.d.ts +1 -1
- package/build/angular/components/switch/shared.js +5 -2
- package/build/angular/components/switch/shared.js.map +1 -1
- package/build/angular/components/text-input.d.ts +8 -2
- package/build/angular/components/text-input.js +79 -34
- package/build/angular/components/text-input.js.map +1 -1
- package/build/angular/components/touchable/index.d.ts +36 -7
- package/build/angular/components/touchable/index.js +280 -72
- package/build/angular/components/touchable/index.js.map +1 -1
- package/build/angular/components/touchable-native-feedback/index.d.ts +2 -1
- package/build/angular/components/touchable-native-feedback/index.js +19 -17
- package/build/angular/components/touchable-native-feedback/index.js.map +1 -1
- package/build/angular/components/virtualized-list/directives.js +4 -0
- package/build/angular/components/virtualized-list/directives.js.map +1 -1
- package/build/angular/components/virtualized-list/index.d.ts +8 -2
- package/build/angular/components/virtualized-list/index.js +207 -61
- package/build/angular/components/virtualized-list/index.js.map +1 -1
- package/build/angular/components/virtualized-section-list/directives.js.map +1 -1
- package/build/angular/components/virtualized-section-list/index.d.ts +21 -1
- package/build/angular/components/virtualized-section-list/index.js +100 -22
- package/build/angular/components/virtualized-section-list/index.js.map +1 -1
- package/build/angular/components.d.ts +2 -2
- package/build/angular/components.js +1 -1
- package/build/angular/components.js.map +1 -1
- package/build/angular/create-portal/index.d.ts +11 -2
- package/build/angular/create-portal/index.js +42 -12
- package/build/angular/create-portal/index.js.map +1 -1
- package/build/angular/create-tunnel/index.js.map +1 -1
- package/build/angular/descriptor-to-angular/index.js +3 -1
- package/build/angular/descriptor-to-angular/index.js.map +1 -1
- package/build/angular/diagnostics.d.ts +33 -0
- package/build/angular/diagnostics.js +82 -0
- package/build/angular/diagnostics.js.map +1 -0
- package/build/angular/gate-demand.d.ts +37 -0
- package/build/angular/gate-demand.js +84 -0
- package/build/angular/gate-demand.js.map +1 -0
- package/build/angular/host-instance/index.js +1 -1
- package/build/angular/host-instance/index.js.map +1 -1
- package/build/angular/index.d.ts +6 -2
- package/build/angular/index.js +16 -1
- package/build/angular/index.js.map +1 -1
- package/build/angular/modules/animated/create-animated-component.d.ts +1 -0
- package/build/angular/modules/animated/create-animated-component.js +41 -18
- package/build/angular/modules/animated/create-animated-component.js.map +1 -1
- package/build/angular/modules/app-registry/index.js.map +1 -1
- package/build/angular/modules/status-bar/index.js +1 -1
- package/build/angular/modules/status-bar/index.js.map +1 -1
- package/build/angular/primitives/index.d.ts +31 -2
- package/build/angular/primitives/index.js +81 -3
- package/build/angular/primitives/index.js.map +1 -1
- package/build/angular/primitives/shared.d.ts +9 -1
- package/build/angular/primitives/shared.js +49 -3
- package/build/angular/primitives/shared.js.map +1 -1
- package/build/angular/register.d.ts +1 -0
- package/build/angular/register.js +35 -0
- package/build/angular/register.js.map +1 -0
- package/build/angular/render/index.js +51 -6
- package/build/angular/render/index.js.map +1 -1
- package/build/angular/renderer/index.d.ts +1 -0
- package/build/angular/renderer/index.js +157 -21
- package/build/angular/renderer/index.js.map +1 -1
- package/build/angular/services/color-scheme.service.js.map +1 -1
- package/build/angular/services/window-dimensions.service.js +1 -1
- package/build/angular/services/window-dimensions.service.js.map +1 -1
- package/build/angular/state-style.d.ts +9 -0
- package/build/angular/state-style.js +39 -0
- package/build/angular/state-style.js.map +1 -0
- package/metro-transformer.cjs +69 -0
- package/package.json +27 -8
- package/src/anchor-host-registry.ts +3 -1
- package/src/bootstrap.ts +4 -1
- package/src/components/activity-indicator/index.android.ts +7 -2
- package/src/components/activity-indicator/index.ios.ts +3 -1
- package/src/components/activity-indicator/shared.ts +32 -13
- package/src/components/button.ts +36 -6
- package/src/components/flat-list/index.ts +91 -29
- package/src/components/image/index.android.ts +9 -6
- package/src/components/image/index.ios.ts +9 -6
- package/src/components/image/shared.ts +110 -45
- package/src/components/image-background.ts +47 -10
- package/src/components/input-accessory-view/index.ts +41 -18
- package/src/components/keyboard-avoiding-view/index.ts +76 -22
- package/src/components/modal/index.ts +39 -12
- package/src/components/pressable/index.ts +121 -31
- package/src/components/refresh-control/index.ts +36 -13
- package/src/components/safe-area-view/index.ts +44 -19
- package/src/components/scroll-view/index.android.ts +31 -9
- package/src/components/scroll-view/index.ios.ts +17 -4
- package/src/components/scroll-view/projection.ts +290 -63
- package/src/components/scroll-view/shared.ts +124 -30
- package/src/components/scroll-view/sticky-header.ts +25 -8
- package/src/components/section-list/index.ts +79 -19
- package/src/components/switch/index.android.ts +18 -7
- package/src/components/switch/index.ios.ts +18 -7
- package/src/components/switch/shared.ts +15 -4
- package/src/components/text-input.ts +119 -31
- package/src/components/touchable/index.ts +339 -72
- package/src/components/touchable-native-feedback/index.ts +50 -18
- package/src/components/virtualized-list/directives.ts +19 -5
- package/src/components/virtualized-list/index.ts +292 -72
- package/src/components/virtualized-section-list/directives.ts +6 -3
- package/src/components/virtualized-section-list/index.ts +164 -34
- package/src/components.ts +6 -1
- package/src/create-portal/index.ts +47 -11
- package/src/create-tunnel/index.ts +3 -1
- package/src/descriptor-to-angular/index.ts +29 -8
- package/src/diagnostics.ts +138 -0
- package/src/gate-demand.ts +109 -0
- package/src/host-instance/index.ts +9 -3
- package/src/index.ts +33 -2
- package/src/modules/animated/create-animated-component.ts +67 -19
- package/src/modules/app-registry/index.ts +3 -1
- package/src/modules/status-bar/index.ts +13 -4
- package/src/primitives/index.ts +73 -2
- package/src/primitives/shared.ts +70 -9
- package/src/register.ts +43 -0
- package/src/render/index.ts +59 -7
- package/src/renderer/index.ts +209 -37
- package/src/services/color-scheme.service.ts +3 -1
- package/src/services/window-dimensions.service.ts +11 -4
- package/src/state-style.ts +39 -0
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
// to 1 on press-out, driven from Pressable's onPressIn/onPressOut. The opacity Value is held by
|
|
7
7
|
// IDENTITY as a plain field (an engine object, never an @Input / reactive wrap) and the
|
|
8
8
|
// <symbiote-animated-view> leaf commits it every frame.
|
|
9
|
-
// TouchableHighlight: paint underlayColor + lower child opacity while
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// TouchableHighlight: paint underlayColor + lower child opacity while the underlay is SHOWN.
|
|
10
|
+
// RN drives that from onPressIn/onPress/onPressOut and holds it past the tap for delayPressOut,
|
|
11
|
+
// NOT from Pressable's pressed flag — a tap too fast to see is already un-pressed by then.
|
|
12
|
+
// TouchableWithoutFeedback: no visual change, but RN still runs the full press-timing machine.
|
|
12
13
|
//
|
|
13
14
|
// Every Touchable's own press/hover/accessibility events are now real @Output() EventEmitters too,
|
|
14
15
|
// matching Pressable (which they wrap) — `(press)="handler($event)"`, never `[onPress]="handler"`.
|
|
@@ -33,33 +34,51 @@ import {
|
|
|
33
34
|
inject,
|
|
34
35
|
Input,
|
|
35
36
|
type OnChanges,
|
|
37
|
+
type OnDestroy,
|
|
36
38
|
Output,
|
|
37
39
|
signal,
|
|
38
40
|
} from '@angular/core';
|
|
39
41
|
import {
|
|
42
|
+
createHighlightUnderlayHandlers,
|
|
43
|
+
createHighlightUnderlayRuntime,
|
|
40
44
|
createTouchableFeedbackHandlers,
|
|
41
45
|
createTouchableFeedbackRuntime,
|
|
42
|
-
|
|
46
|
+
hasTouchablePressHandler,
|
|
47
|
+
resolveHighlightExtraStyles,
|
|
48
|
+
restingOpacityFromStyle,
|
|
43
49
|
DEFAULT_ACTIVE_OPACITY,
|
|
44
|
-
|
|
45
|
-
DEFAULT_MIN_PRESS_DURATION_MS,
|
|
46
|
-
DEFAULT_UNDERLAY_COLOR,
|
|
47
|
-
OPACITY_ACTIVE_DURATION_MS,
|
|
50
|
+
OPACITY_ACTIVE_GRANT_DURATION_MS,
|
|
48
51
|
OPACITY_INACTIVE_DURATION_MS,
|
|
49
52
|
RESTING_OPACITY,
|
|
53
|
+
TOUCHABLE_MIN_PRESS_DURATION_MS,
|
|
50
54
|
type IAccessibilityProps,
|
|
51
55
|
type IAccessibilityStateValue,
|
|
52
56
|
type IAriaProps,
|
|
57
|
+
type IHighlightUnderlayHandlers,
|
|
53
58
|
type IPressableAndroidRippleConfig,
|
|
54
|
-
type IPressState,
|
|
55
59
|
type IPressTimingProps,
|
|
56
60
|
type IRectOffset,
|
|
57
61
|
type ITouchableFeedbackHandlers,
|
|
58
62
|
} from '@symbiote-native/components';
|
|
59
|
-
import {
|
|
60
|
-
|
|
63
|
+
import {
|
|
64
|
+
type ISymbioteEvent,
|
|
65
|
+
type IStyleProp,
|
|
66
|
+
type IViewStyle,
|
|
67
|
+
} from '@symbiote-native/engine';
|
|
68
|
+
import {
|
|
69
|
+
anchorHostStyle,
|
|
70
|
+
anchorStyleProp,
|
|
71
|
+
SymbioteStyleInputDirective,
|
|
72
|
+
} from '../../primitives';
|
|
61
73
|
import { Pressable, type IAngularPressableInputs } from '../pressable';
|
|
62
74
|
import { Animated, AnimatedView } from '../../modules/animated';
|
|
75
|
+
import {
|
|
76
|
+
gateWanted,
|
|
77
|
+
injectGateDemandAbove,
|
|
78
|
+
provideGateDemand,
|
|
79
|
+
type IGateDemand,
|
|
80
|
+
type IGatedAccessibilityEvent,
|
|
81
|
+
} from '../../gate-demand';
|
|
63
82
|
|
|
64
83
|
// The shared field base for all three: the Pressable INPUT surface (minus style, which each
|
|
65
84
|
// Touchable routes differently, and minus the press/hover events, which each Touchable declares as
|
|
@@ -82,22 +101,50 @@ export type IAngularTouchableHighlightProps = IAngularTouchableBaseProps & {
|
|
|
82
101
|
|
|
83
102
|
export type IAngularTouchableWithoutFeedbackProps = IAngularTouchableBaseProps;
|
|
84
103
|
|
|
85
|
-
// The real
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
104
|
+
// The real timers both shared machines schedule on — core/components carries no timer globals, so
|
|
105
|
+
// scheduling is the adapter's half. Every canceller is retained so ngOnDestroy cancels what is
|
|
106
|
+
// still in flight: a deferred press-out or a held underlay firing after teardown would emit from a
|
|
107
|
+
// destroyed component and write a signal on a destroyed view.
|
|
108
|
+
interface ITimerScheduler {
|
|
109
|
+
schedule: (callback: () => void, ms: number) => () => void;
|
|
110
|
+
cancelAll: () => void;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function createTimerScheduler(): ITimerScheduler {
|
|
114
|
+
const pending = new Set<() => void>();
|
|
115
|
+
return {
|
|
116
|
+
schedule(callback: () => void, ms: number): () => void {
|
|
117
|
+
const id = setTimeout(() => {
|
|
118
|
+
pending.delete(cancel);
|
|
119
|
+
callback();
|
|
120
|
+
}, ms);
|
|
121
|
+
const cancel = (): void => {
|
|
122
|
+
clearTimeout(id);
|
|
123
|
+
pending.delete(cancel);
|
|
124
|
+
};
|
|
125
|
+
pending.add(cancel);
|
|
126
|
+
return cancel;
|
|
127
|
+
},
|
|
128
|
+
cancelAll(): void {
|
|
129
|
+
for (const cancel of [...pending]) cancel();
|
|
130
|
+
pending.clear();
|
|
131
|
+
},
|
|
132
|
+
};
|
|
90
133
|
}
|
|
91
134
|
|
|
92
135
|
@Component({
|
|
93
136
|
selector: 'TouchableOpacity',
|
|
94
137
|
standalone: true,
|
|
95
|
-
|
|
138
|
+
viewProviders: [provideGateDemand(() => TouchableOpacity)],
|
|
139
|
+
hostDirectives: [
|
|
140
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
141
|
+
],
|
|
96
142
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
97
143
|
imports: [Pressable, AnimatedView],
|
|
98
144
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
99
145
|
template: `
|
|
100
146
|
<Pressable
|
|
147
|
+
[__minPressDuration]="0"
|
|
101
148
|
(press)="press.emit($event)"
|
|
102
149
|
(pressIn)="handlePressIn($event)"
|
|
103
150
|
(pressOut)="handlePressOut($event)"
|
|
@@ -138,8 +185,12 @@ function scheduleTimeout(callback: () => void, ms: number): () => void {
|
|
|
138
185
|
[accessibilityElementsHidden]="accessibilityElementsHidden"
|
|
139
186
|
[accessibilityIgnoresInvertColors]="accessibilityIgnoresInvertColors"
|
|
140
187
|
[accessibilityLanguage]="accessibilityLanguage"
|
|
141
|
-
[accessibilityRespondsToUserInteraction]="
|
|
142
|
-
|
|
188
|
+
[accessibilityRespondsToUserInteraction]="
|
|
189
|
+
accessibilityRespondsToUserInteraction
|
|
190
|
+
"
|
|
191
|
+
[accessibilityShowsLargeContentViewer]="
|
|
192
|
+
accessibilityShowsLargeContentViewer
|
|
193
|
+
"
|
|
143
194
|
[accessibilityLargeContentTitle]="accessibilityLargeContentTitle"
|
|
144
195
|
(accessibilityAction)="accessibilityAction.emit($event)"
|
|
145
196
|
(accessibilityTap)="accessibilityTap.emit($event)"
|
|
@@ -168,7 +219,9 @@ function scheduleTimeout(callback: () => void, ms: number): () => void {
|
|
|
168
219
|
</Pressable>
|
|
169
220
|
`,
|
|
170
221
|
})
|
|
171
|
-
export class TouchableOpacity
|
|
222
|
+
export class TouchableOpacity
|
|
223
|
+
implements IAngularTouchableOpacityProps, OnChanges, OnDestroy, DoCheck
|
|
224
|
+
{
|
|
172
225
|
@Output() readonly press = new EventEmitter<ISymbioteEvent>();
|
|
173
226
|
@Output() readonly pressIn = new EventEmitter<ISymbioteEvent>();
|
|
174
227
|
@Output() readonly pressOut = new EventEmitter<ISymbioteEvent>();
|
|
@@ -180,6 +233,16 @@ export class TouchableOpacity implements IAngularTouchableOpacityProps, DoCheck
|
|
|
180
233
|
@Output() readonly accessibilityTap = new EventEmitter<ISymbioteEvent>();
|
|
181
234
|
@Output() readonly magicTap = new EventEmitter<ISymbioteEvent>();
|
|
182
235
|
@Output() readonly accessibilityEscape = new EventEmitter<ISymbioteEvent>();
|
|
236
|
+
|
|
237
|
+
// This wrapper binds the four gated accessibility events on the component it renders, which
|
|
238
|
+
// Angular forces to be unconditional and which would light that component's gates on every
|
|
239
|
+
// instance. It answers for them instead — see `gate-demand.ts`.
|
|
240
|
+
private readonly gateDemandAbove = injectGateDemandAbove();
|
|
241
|
+
|
|
242
|
+
wantsGate(name: IGatedAccessibilityEvent): boolean {
|
|
243
|
+
return gateWanted(this.gateDemandAbove, name, this[name]);
|
|
244
|
+
}
|
|
245
|
+
|
|
183
246
|
@Input() activeOpacity?: number;
|
|
184
247
|
@Input() delayPressIn?: number;
|
|
185
248
|
@Input() delayPressOut?: number;
|
|
@@ -211,8 +274,10 @@ export class TouchableOpacity implements IAngularTouchableOpacityProps, DoCheck
|
|
|
211
274
|
@Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
|
|
212
275
|
@Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
|
|
213
276
|
@Input() accessibilityLabelledBy?: string | string[];
|
|
214
|
-
@Input()
|
|
215
|
-
|
|
277
|
+
@Input()
|
|
278
|
+
importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
|
|
279
|
+
@Input()
|
|
280
|
+
accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
|
|
216
281
|
@Input() screenReaderFocusable?: boolean;
|
|
217
282
|
@Input() accessibilityViewIsModal?: boolean;
|
|
218
283
|
@Input() accessibilityElementsHidden?: boolean;
|
|
@@ -238,13 +303,21 @@ export class TouchableOpacity implements IAngularTouchableOpacityProps, DoCheck
|
|
|
238
303
|
@Input() id?: string;
|
|
239
304
|
@Input() role?: IAriaProps['role'];
|
|
240
305
|
|
|
241
|
-
// One Animated.Value per mount
|
|
242
|
-
//
|
|
243
|
-
//
|
|
306
|
+
// One Animated.Value per mount. Held by IDENTITY as a plain field (an engine object, never an
|
|
307
|
+
// @Input / reactive wrap); the <symbiote-animated-view> leaf rasterizes it for the first paint
|
|
308
|
+
// and drives it through setNativeProps every frame.
|
|
309
|
+
//
|
|
310
|
+
// RN seeds it from _getChildStyleOpacityWithDefault(props.style). Angular writes @Input()s AFTER
|
|
311
|
+
// the field initializers run, so `style` is still undefined here — the real resting value is
|
|
312
|
+
// seeded by the first ngOnChanges below, which still precedes the first template pass.
|
|
244
313
|
private readonly opacity = new Animated.Value(RESTING_OPACITY);
|
|
245
314
|
// The shared press-scheduling cell (delayPressIn timer + activation clock), persisted on the
|
|
246
315
|
// instance; the machine's handlers are rebuilt per event over live @Input()s.
|
|
247
316
|
private readonly runtime = createTouchableFeedbackRuntime();
|
|
317
|
+
private readonly timers = createTimerScheduler();
|
|
318
|
+
// What the last settle ran for. Undefined until the first ngOnChanges, which is the MOUNT.
|
|
319
|
+
private settled:
|
|
320
|
+
{ disabled: boolean | undefined; resting: number } | undefined;
|
|
248
321
|
// This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
|
|
249
322
|
// onto (see anchorHostStyle's doc comment) — NOT the <symbiote-animated-view> leaf one level down.
|
|
250
323
|
private readonly elementRef = inject(ElementRef);
|
|
@@ -271,12 +344,46 @@ export class TouchableOpacity implements IAngularTouchableOpacityProps, DoCheck
|
|
|
271
344
|
this.anchorStyle.set(anchorHostStyle(this.elementRef));
|
|
272
345
|
}
|
|
273
346
|
|
|
347
|
+
// RN's componentDidUpdate: a changed `disabled` or a changed style opacity re-settles the view,
|
|
348
|
+
// so a Touchable disabled mid-press does not stay stuck at its active opacity. The FIRST call is
|
|
349
|
+
// the mount (ngOnChanges runs before ngOnInit and before the first template pass), so it seeds
|
|
350
|
+
// the Value instead of animating — RN does this on update only, and a fade at mount would animate
|
|
351
|
+
// away from the value the leaf is about to paint. A use site with no bindings gets no ngOnChanges
|
|
352
|
+
// at all, and there the RESTING_OPACITY seed is already the right answer.
|
|
353
|
+
ngOnChanges(): void {
|
|
354
|
+
const settled = { disabled: this.disabled, resting: this.restingOpacity() };
|
|
355
|
+
if (this.settled === undefined) {
|
|
356
|
+
this.opacity.setValue(settled.resting);
|
|
357
|
+
} else if (
|
|
358
|
+
this.settled.disabled !== settled.disabled ||
|
|
359
|
+
this.settled.resting !== settled.resting
|
|
360
|
+
) {
|
|
361
|
+
this.setOpacityTo(settled.resting, OPACITY_INACTIVE_DURATION_MS);
|
|
362
|
+
}
|
|
363
|
+
this.settled = settled;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// RN's componentWillUnmount: stop the animation so a teardown mid-fade leaves no driver running
|
|
367
|
+
// against a node that is gone, and drop any press-out still deferred behind a timer.
|
|
368
|
+
ngOnDestroy(): void {
|
|
369
|
+
this.timers.cancelAll();
|
|
370
|
+
this.opacity.resetAnimation();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// RN's _getChildStyleOpacityWithDefault: the fade settles at the opacity the CALLER's style asks
|
|
374
|
+
// for, not at a hard 1 — a Touchable styled `opacity: 0.6` must come back to 0.6.
|
|
375
|
+
private restingOpacity(): number {
|
|
376
|
+
return restingOpacityFromStyle(this.style);
|
|
377
|
+
}
|
|
378
|
+
|
|
274
379
|
private setOpacityTo(toValue: number, duration: number): void {
|
|
275
380
|
Animated.timing(this.opacity, {
|
|
276
381
|
toValue,
|
|
277
382
|
duration,
|
|
278
383
|
easing: Animated.Easing.inOut(Animated.Easing.quad),
|
|
279
|
-
|
|
384
|
+
// RN's own (TouchableOpacity.js:242). opacity is natively drivable, so the fade survives a
|
|
385
|
+
// busy JS thread — which is the entire point of press feedback.
|
|
386
|
+
useNativeDriver: true,
|
|
280
387
|
}).start();
|
|
281
388
|
}
|
|
282
389
|
|
|
@@ -288,8 +395,11 @@ export class TouchableOpacity implements IAngularTouchableOpacityProps, DoCheck
|
|
|
288
395
|
{
|
|
289
396
|
delayPressIn: this.delayPressIn ?? 0,
|
|
290
397
|
delayPressOut: this.delayPressOut ?? 0,
|
|
291
|
-
|
|
292
|
-
|
|
398
|
+
// RN's Touchables override Pressability's own 130ms floor with 0; what holds the active
|
|
399
|
+
// visual there is the fade's own duration, not a press-duration floor.
|
|
400
|
+
minPressDuration:
|
|
401
|
+
this.minPressDuration ?? TOUCHABLE_MIN_PRESS_DURATION_MS,
|
|
402
|
+
schedule: this.timers.schedule,
|
|
293
403
|
now: Date.now,
|
|
294
404
|
},
|
|
295
405
|
this.runtime,
|
|
@@ -297,12 +407,15 @@ export class TouchableOpacity implements IAngularTouchableOpacityProps, DoCheck
|
|
|
297
407
|
activate: (event: ISymbioteEvent): void => {
|
|
298
408
|
this.setOpacityTo(
|
|
299
409
|
this.activeOpacity ?? DEFAULT_ACTIVE_OPACITY,
|
|
300
|
-
|
|
410
|
+
OPACITY_ACTIVE_GRANT_DURATION_MS,
|
|
301
411
|
);
|
|
302
412
|
this.pressIn.emit(event);
|
|
303
413
|
},
|
|
304
414
|
deactivate: (event: ISymbioteEvent): void => {
|
|
305
|
-
this.setOpacityTo(
|
|
415
|
+
this.setOpacityTo(
|
|
416
|
+
this.restingOpacity(),
|
|
417
|
+
OPACITY_INACTIVE_DURATION_MS,
|
|
418
|
+
);
|
|
306
419
|
this.pressOut.emit(event);
|
|
307
420
|
},
|
|
308
421
|
},
|
|
@@ -323,15 +436,19 @@ export class TouchableOpacity implements IAngularTouchableOpacityProps, DoCheck
|
|
|
323
436
|
@Component({
|
|
324
437
|
selector: 'TouchableHighlight',
|
|
325
438
|
standalone: true,
|
|
326
|
-
|
|
439
|
+
viewProviders: [provideGateDemand(() => TouchableHighlight)],
|
|
440
|
+
hostDirectives: [
|
|
441
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
442
|
+
],
|
|
327
443
|
imports: [Pressable],
|
|
328
444
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
329
445
|
template: `
|
|
330
446
|
<Pressable
|
|
331
|
-
[
|
|
332
|
-
|
|
333
|
-
(
|
|
334
|
-
(
|
|
447
|
+
[__minPressDuration]="0"
|
|
448
|
+
[style]="containerStyle()"
|
|
449
|
+
(press)="handlePress($event)"
|
|
450
|
+
(pressIn)="handlePressIn($event)"
|
|
451
|
+
(pressOut)="handlePressOut($event)"
|
|
335
452
|
(pressMove)="pressMove.emit($event)"
|
|
336
453
|
(longPress)="longPress.emit($event)"
|
|
337
454
|
(hoverIn)="hoverIn.emit($event)"
|
|
@@ -369,8 +486,12 @@ export class TouchableOpacity implements IAngularTouchableOpacityProps, DoCheck
|
|
|
369
486
|
[accessibilityElementsHidden]="accessibilityElementsHidden"
|
|
370
487
|
[accessibilityIgnoresInvertColors]="accessibilityIgnoresInvertColors"
|
|
371
488
|
[accessibilityLanguage]="accessibilityLanguage"
|
|
372
|
-
[accessibilityRespondsToUserInteraction]="
|
|
373
|
-
|
|
489
|
+
[accessibilityRespondsToUserInteraction]="
|
|
490
|
+
accessibilityRespondsToUserInteraction
|
|
491
|
+
"
|
|
492
|
+
[accessibilityShowsLargeContentViewer]="
|
|
493
|
+
accessibilityShowsLargeContentViewer
|
|
494
|
+
"
|
|
374
495
|
[accessibilityLargeContentTitle]="accessibilityLargeContentTitle"
|
|
375
496
|
(accessibilityAction)="accessibilityAction.emit($event)"
|
|
376
497
|
(accessibilityTap)="accessibilityTap.emit($event)"
|
|
@@ -397,7 +518,9 @@ export class TouchableOpacity implements IAngularTouchableOpacityProps, DoCheck
|
|
|
397
518
|
</Pressable>
|
|
398
519
|
`,
|
|
399
520
|
})
|
|
400
|
-
export class TouchableHighlight
|
|
521
|
+
export class TouchableHighlight
|
|
522
|
+
implements IAngularTouchableHighlightProps, OnChanges, OnDestroy, DoCheck
|
|
523
|
+
{
|
|
401
524
|
@Output() readonly press = new EventEmitter<ISymbioteEvent>();
|
|
402
525
|
@Output() readonly pressIn = new EventEmitter<ISymbioteEvent>();
|
|
403
526
|
@Output() readonly pressOut = new EventEmitter<ISymbioteEvent>();
|
|
@@ -409,6 +532,19 @@ export class TouchableHighlight implements IAngularTouchableHighlightProps, OnCh
|
|
|
409
532
|
@Output() readonly accessibilityTap = new EventEmitter<ISymbioteEvent>();
|
|
410
533
|
@Output() readonly magicTap = new EventEmitter<ISymbioteEvent>();
|
|
411
534
|
@Output() readonly accessibilityEscape = new EventEmitter<ISymbioteEvent>();
|
|
535
|
+
|
|
536
|
+
// This wrapper binds the four gated accessibility events on the component it renders, which
|
|
537
|
+
// Angular forces to be unconditional and which would light that component's gates on every
|
|
538
|
+
// instance. It answers for them instead — see `gate-demand.ts`.
|
|
539
|
+
private readonly gateDemandAbove = injectGateDemandAbove();
|
|
540
|
+
|
|
541
|
+
wantsGate(name: IGatedAccessibilityEvent): boolean {
|
|
542
|
+
return gateWanted(this.gateDemandAbove, name, this[name]);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// RN's onShowUnderlay / onHideUnderlay — fired on a real transition only, never on a repeat.
|
|
546
|
+
@Output() readonly showUnderlay = new EventEmitter<void>();
|
|
547
|
+
@Output() readonly hideUnderlay = new EventEmitter<void>();
|
|
412
548
|
@Input() activeOpacity?: number;
|
|
413
549
|
@Input() underlayColor?: string;
|
|
414
550
|
@Input() delayPressIn?: number;
|
|
@@ -441,8 +577,10 @@ export class TouchableHighlight implements IAngularTouchableHighlightProps, OnCh
|
|
|
441
577
|
@Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
|
|
442
578
|
@Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
|
|
443
579
|
@Input() accessibilityLabelledBy?: string | string[];
|
|
444
|
-
@Input()
|
|
445
|
-
|
|
580
|
+
@Input()
|
|
581
|
+
importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
|
|
582
|
+
@Input()
|
|
583
|
+
accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
|
|
446
584
|
@Input() screenReaderFocusable?: boolean;
|
|
447
585
|
@Input() accessibilityViewIsModal?: boolean;
|
|
448
586
|
@Input() accessibilityElementsHidden?: boolean;
|
|
@@ -472,33 +610,102 @@ export class TouchableHighlight implements IAngularTouchableHighlightProps, OnCh
|
|
|
472
610
|
// onto (see anchorHostStyle's doc comment) — NOT the Pressable one level down.
|
|
473
611
|
private readonly elementRef = inject(ElementRef);
|
|
474
612
|
|
|
475
|
-
//
|
|
476
|
-
//
|
|
477
|
-
//
|
|
478
|
-
//
|
|
479
|
-
//
|
|
480
|
-
//
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
//
|
|
486
|
-
//
|
|
487
|
-
//
|
|
488
|
-
|
|
613
|
+
// `shown` is NOT Pressable's `pressed`. RN holds the underlay for delayPressOut past the tap so a
|
|
614
|
+
// press too fast to see still flashes, and the pressed flag is already false by then. A SIGNAL,
|
|
615
|
+
// because the hide fires from a timer — outside any Angular listener, so under zoneless CD
|
|
616
|
+
// nothing else would mark this view. A signal write both notifies the change-detection scheduler
|
|
617
|
+
// (markAncestorsForTraversal) and dirties this template's own reactive consumer, which is the one
|
|
618
|
+
// thing honored in Targeted mode; markForCheck from a plain field would not be reachable here.
|
|
619
|
+
private readonly shown = signal(false);
|
|
620
|
+
private readonly underlayRuntime = createHighlightUnderlayRuntime();
|
|
621
|
+
private readonly timers = createTimerScheduler();
|
|
622
|
+
|
|
623
|
+
// The container style Pressable paints.
|
|
624
|
+
//
|
|
625
|
+
// POINT-7 DECISION — both halves stay on the CONTAINER, deliberately. RN's _createExtraStyles
|
|
626
|
+
// keeps them apart: `underlay` (the backgroundColor) on the container, `child` (the lowered
|
|
627
|
+
// opacity) cloned onto the single child. Angular has no cloneElement and no way to reach a
|
|
628
|
+
// projected <ng-content> node, so the only path to the child is a permanent wrapper View — which
|
|
629
|
+
// inserts a node into the flex chain and silently re-parents any `flex` a child declares. The
|
|
630
|
+
// fake Fabric runs no Yoga, so no headless test can measure that damage, only assert the extra
|
|
631
|
+
// node exists; shipping an unmeasurable layout change is worse than the visual approximation.
|
|
632
|
+
// Same call, same reason, as the Solid adapter. resolveHighlightExtraStyles keeps the two styles
|
|
633
|
+
// SEPARATE regardless, so a layout-safe fix (which needs a device check, not a test) stays open.
|
|
634
|
+
//
|
|
635
|
+
// Returns a NEW ARRAY whenever a dependency moves: Pressable's `style` @Input then reports an
|
|
636
|
+
// ordinary change. A stable reference was the earlier bug — Pressable's view never refreshed, so
|
|
637
|
+
// a class or [style] toggled after mount never reached the committed view.
|
|
638
|
+
readonly containerStyle = computed<IStyleProp<IViewStyle>>(() => {
|
|
489
639
|
this.inputsRevision();
|
|
490
640
|
const base: IStyleProp<IViewStyle> = [this.anchorStyle(), this.style];
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
641
|
+
const extra = resolveHighlightExtraStyles({
|
|
642
|
+
shown: this.shown(),
|
|
643
|
+
hasPressHandler: this.hasPressHandler(),
|
|
644
|
+
underlayColor: this.underlayColor,
|
|
645
|
+
activeOpacity: this.activeOpacity,
|
|
646
|
+
});
|
|
647
|
+
return extra === undefined ? base : [base, extra.underlay, extra.child];
|
|
495
648
|
});
|
|
496
649
|
|
|
497
|
-
//
|
|
650
|
+
// RN's _hasPressHandler: a Touchable that handles no press paints no underlay. Angular exposes
|
|
651
|
+
// the press lifecycle as @Output()s, so "a handler is present" is "something is subscribed" — the
|
|
652
|
+
// same `.observed` test Pressable uses to decide whether to arm its long-press timer at all.
|
|
653
|
+
private hasPressHandler(): boolean {
|
|
654
|
+
return hasTouchablePressHandler({
|
|
655
|
+
onPress: this.press.observed ? this.press : undefined,
|
|
656
|
+
onPressIn: this.pressIn.observed ? this.pressIn : undefined,
|
|
657
|
+
onPressOut: this.pressOut.observed ? this.pressOut : undefined,
|
|
658
|
+
onLongPress: this.longPress.observed ? this.longPress : undefined,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Built per event so the machine reads live @Input()s (delayPressOut, the has-handler gate); the
|
|
663
|
+
// runtime persists across calls, which is what lets handlePressOut see the timer handlePress set.
|
|
664
|
+
private underlayHandlers(): IHighlightUnderlayHandlers {
|
|
665
|
+
return createHighlightUnderlayHandlers(
|
|
666
|
+
{
|
|
667
|
+
delayPressOut: this.delayPressOut ?? 0,
|
|
668
|
+
hasPressHandler: this.hasPressHandler(),
|
|
669
|
+
schedule: this.timers.schedule,
|
|
670
|
+
},
|
|
671
|
+
this.underlayRuntime,
|
|
672
|
+
{
|
|
673
|
+
setShown: (shown: boolean): void => this.shown.set(shown),
|
|
674
|
+
onShowUnderlay: (): void => this.showUnderlay.emit(),
|
|
675
|
+
onHideUnderlay: (): void => this.hideUnderlay.emit(),
|
|
676
|
+
},
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Visual first, then the caller's own event — RN's order in _createPressabilityConfig.
|
|
681
|
+
// onLongPress is deliberately NOT intercepted (Pressable emits it straight through); it is only
|
|
682
|
+
// READ, by the has-press-handler gate above.
|
|
683
|
+
handlePressIn(event: ISymbioteEvent): void {
|
|
684
|
+
this.underlayHandlers().handlePressIn(event);
|
|
685
|
+
this.pressIn.emit(event);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
handlePress(event: ISymbioteEvent): void {
|
|
689
|
+
this.underlayHandlers().handlePress(event);
|
|
690
|
+
this.press.emit(event);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
handlePressOut(event: ISymbioteEvent): void {
|
|
694
|
+
this.underlayHandlers().handlePressOut(event);
|
|
695
|
+
this.pressOut.emit(event);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
ngOnDestroy(): void {
|
|
699
|
+
this.timers.cancelAll();
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// `style`, `underlayColor` and `activeOpacity` are read untracked inside containerStyle, so the
|
|
498
703
|
// bump is what tells it they moved. See the ngDoCheck note below for the anchor half, which
|
|
499
704
|
// ngOnChanges cannot cover.
|
|
500
705
|
private readonly inputsRevision = signal(0);
|
|
501
|
-
private readonly anchorStyle = signal<IStyleProp<IViewStyle> | undefined>(
|
|
706
|
+
private readonly anchorStyle = signal<IStyleProp<IViewStyle> | undefined>(
|
|
707
|
+
undefined,
|
|
708
|
+
);
|
|
502
709
|
|
|
503
710
|
ngOnChanges(): void {
|
|
504
711
|
this.inputsRevision.update(revision => revision + 1);
|
|
@@ -517,15 +724,19 @@ export class TouchableHighlight implements IAngularTouchableHighlightProps, OnCh
|
|
|
517
724
|
@Component({
|
|
518
725
|
selector: 'TouchableWithoutFeedback',
|
|
519
726
|
standalone: true,
|
|
520
|
-
|
|
727
|
+
viewProviders: [provideGateDemand(() => TouchableWithoutFeedback)],
|
|
728
|
+
hostDirectives: [
|
|
729
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
730
|
+
],
|
|
521
731
|
imports: [Pressable],
|
|
522
732
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
523
733
|
template: `
|
|
524
734
|
<Pressable
|
|
735
|
+
[__minPressDuration]="0"
|
|
525
736
|
[style]="mergedStyle"
|
|
526
737
|
(press)="press.emit($event)"
|
|
527
|
-
(pressIn)="
|
|
528
|
-
(pressOut)="
|
|
738
|
+
(pressIn)="handlePressIn($event)"
|
|
739
|
+
(pressOut)="handlePressOut($event)"
|
|
529
740
|
(pressMove)="pressMove.emit($event)"
|
|
530
741
|
(longPress)="longPress.emit($event)"
|
|
531
742
|
(hoverIn)="hoverIn.emit($event)"
|
|
@@ -563,8 +774,12 @@ export class TouchableHighlight implements IAngularTouchableHighlightProps, OnCh
|
|
|
563
774
|
[accessibilityElementsHidden]="accessibilityElementsHidden"
|
|
564
775
|
[accessibilityIgnoresInvertColors]="accessibilityIgnoresInvertColors"
|
|
565
776
|
[accessibilityLanguage]="accessibilityLanguage"
|
|
566
|
-
[accessibilityRespondsToUserInteraction]="
|
|
567
|
-
|
|
777
|
+
[accessibilityRespondsToUserInteraction]="
|
|
778
|
+
accessibilityRespondsToUserInteraction
|
|
779
|
+
"
|
|
780
|
+
[accessibilityShowsLargeContentViewer]="
|
|
781
|
+
accessibilityShowsLargeContentViewer
|
|
782
|
+
"
|
|
568
783
|
[accessibilityLargeContentTitle]="accessibilityLargeContentTitle"
|
|
569
784
|
(accessibilityAction)="accessibilityAction.emit($event)"
|
|
570
785
|
(accessibilityTap)="accessibilityTap.emit($event)"
|
|
@@ -591,10 +806,13 @@ export class TouchableHighlight implements IAngularTouchableHighlightProps, OnCh
|
|
|
591
806
|
</Pressable>
|
|
592
807
|
`,
|
|
593
808
|
})
|
|
594
|
-
export class TouchableWithoutFeedback
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
//
|
|
809
|
+
export class TouchableWithoutFeedback
|
|
810
|
+
implements DoCheck, OnDestroy, IAngularTouchableWithoutFeedbackProps
|
|
811
|
+
{
|
|
812
|
+
// RN's TouchableWithoutFeedback builds a FULL Pressability config with delayPressIn /
|
|
813
|
+
// delayPressOut / minPressDuration: 0 — "without feedback" means no VISUAL, not no timing. So the
|
|
814
|
+
// same shared machine TouchableOpacity uses runs here, with the visual half left empty: the
|
|
815
|
+
// pressIn/pressOut EMIT is what gets deferred and floored.
|
|
598
816
|
@Output() readonly press = new EventEmitter<ISymbioteEvent>();
|
|
599
817
|
@Output() readonly pressIn = new EventEmitter<ISymbioteEvent>();
|
|
600
818
|
@Output() readonly pressOut = new EventEmitter<ISymbioteEvent>();
|
|
@@ -606,6 +824,16 @@ export class TouchableWithoutFeedback implements DoCheck, IAngularTouchableWitho
|
|
|
606
824
|
@Output() readonly accessibilityTap = new EventEmitter<ISymbioteEvent>();
|
|
607
825
|
@Output() readonly magicTap = new EventEmitter<ISymbioteEvent>();
|
|
608
826
|
@Output() readonly accessibilityEscape = new EventEmitter<ISymbioteEvent>();
|
|
827
|
+
|
|
828
|
+
// This wrapper binds the four gated accessibility events on the component it renders, which
|
|
829
|
+
// Angular forces to be unconditional and which would light that component's gates on every
|
|
830
|
+
// instance. It answers for them instead — see `gate-demand.ts`.
|
|
831
|
+
private readonly gateDemandAbove = injectGateDemandAbove();
|
|
832
|
+
|
|
833
|
+
wantsGate(name: IGatedAccessibilityEvent): boolean {
|
|
834
|
+
return gateWanted(this.gateDemandAbove, name, this[name]);
|
|
835
|
+
}
|
|
836
|
+
|
|
609
837
|
@Input() delayPressIn?: number;
|
|
610
838
|
@Input() delayPressOut?: number;
|
|
611
839
|
@Input() minPressDuration?: number;
|
|
@@ -636,8 +864,10 @@ export class TouchableWithoutFeedback implements DoCheck, IAngularTouchableWitho
|
|
|
636
864
|
@Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
|
|
637
865
|
@Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
|
|
638
866
|
@Input() accessibilityLabelledBy?: string | string[];
|
|
639
|
-
@Input()
|
|
640
|
-
|
|
867
|
+
@Input()
|
|
868
|
+
importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
|
|
869
|
+
@Input()
|
|
870
|
+
accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
|
|
641
871
|
@Input() screenReaderFocusable?: boolean;
|
|
642
872
|
@Input() accessibilityViewIsModal?: boolean;
|
|
643
873
|
@Input() accessibilityElementsHidden?: boolean;
|
|
@@ -667,12 +897,47 @@ export class TouchableWithoutFeedback implements DoCheck, IAngularTouchableWitho
|
|
|
667
897
|
// onto (see anchorHostStyle's doc comment) — NOT the Pressable one level down.
|
|
668
898
|
private readonly elementRef = inject(ElementRef);
|
|
669
899
|
|
|
900
|
+
// The shared press-scheduling cell (delayPressIn timer + activation clock), persisted on the
|
|
901
|
+
// instance; the machine's handlers are rebuilt per event over live @Input()s.
|
|
902
|
+
private readonly runtime = createTouchableFeedbackRuntime();
|
|
903
|
+
private readonly timers = createTimerScheduler();
|
|
904
|
+
|
|
670
905
|
// The anchor's class-derived style goes first, then the explicit style, so an explicit [style]
|
|
671
906
|
// still beats the ambient class.
|
|
672
907
|
get mergedStyle(): IStyleProp<IViewStyle> {
|
|
673
908
|
return [this.anchorStyle(), this.style];
|
|
674
909
|
}
|
|
675
910
|
|
|
911
|
+
private feedbackHandlers(): ITouchableFeedbackHandlers {
|
|
912
|
+
return createTouchableFeedbackHandlers(
|
|
913
|
+
{
|
|
914
|
+
delayPressIn: this.delayPressIn ?? 0,
|
|
915
|
+
delayPressOut: this.delayPressOut ?? 0,
|
|
916
|
+
minPressDuration:
|
|
917
|
+
this.minPressDuration ?? TOUCHABLE_MIN_PRESS_DURATION_MS,
|
|
918
|
+
schedule: this.timers.schedule,
|
|
919
|
+
now: Date.now,
|
|
920
|
+
},
|
|
921
|
+
this.runtime,
|
|
922
|
+
{
|
|
923
|
+
activate: (event: ISymbioteEvent): void => this.pressIn.emit(event),
|
|
924
|
+
deactivate: (event: ISymbioteEvent): void => this.pressOut.emit(event),
|
|
925
|
+
},
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
handlePressIn(event: ISymbioteEvent): void {
|
|
930
|
+
this.feedbackHandlers().handlePressIn(event);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
handlePressOut(event: ISymbioteEvent): void {
|
|
934
|
+
this.feedbackHandlers().handlePressOut(event);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
ngOnDestroy(): void {
|
|
938
|
+
this.timers.cancelAll();
|
|
939
|
+
}
|
|
940
|
+
|
|
676
941
|
// The anchor's class-derived style is written by the renderer's addClass/removeClass at the USE
|
|
677
942
|
// SITE - it never appears in SimpleChanges, and nothing about it dirties THIS component's view.
|
|
678
943
|
// A `class=` present at creation therefore worked, while one toggled later did not: the parent
|
|
@@ -681,7 +946,9 @@ export class TouchableWithoutFeedback implements DoCheck, IAngularTouchableWitho
|
|
|
681
946
|
// ngDoCheck fixes both halves: ngDoCheck runs during the PARENT's refresh even when this view is
|
|
682
947
|
// skipped, and the signal write is what then marks this view for refresh. `signal.set`'s own
|
|
683
948
|
// Object.is makes an unchanged poll a no-op, so there is no loop.
|
|
684
|
-
private readonly anchorStyle = signal<IStyleProp<IViewStyle> | undefined>(
|
|
949
|
+
private readonly anchorStyle = signal<IStyleProp<IViewStyle> | undefined>(
|
|
950
|
+
undefined,
|
|
951
|
+
);
|
|
685
952
|
|
|
686
953
|
ngDoCheck(): void {
|
|
687
954
|
this.anchorStyle.set(anchorStyleProp<IViewStyle>(this.elementRef));
|