@symbiote-native/angular 0.6.2 → 0.8.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/bin/symbiote-angular-dev.cjs +20 -19
- package/build/angular/anchor-host-registry.js +2 -2
- 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.d.ts +2 -1
- package/build/angular/components/activity-indicator/index.android.js +12 -4
- package/build/angular/components/activity-indicator/index.android.js.map +1 -1
- package/build/angular/components/activity-indicator/index.ios.d.ts +2 -1
- package/build/angular/components/activity-indicator/index.ios.js +8 -3
- 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 +4 -4
- package/build/angular/components/activity-indicator/shared.js.map +1 -1
- package/build/angular/components/button.d.ts +4 -2
- package/build/angular/components/button.js +41 -22
- package/build/angular/components/button.js.map +1 -1
- package/build/angular/components/flat-list/index.d.ts +6 -4
- package/build/angular/components/flat-list/index.js +80 -50
- package/build/angular/components/flat-list/index.js.map +1 -1
- package/build/angular/components/image/index.android.d.ts +8 -3
- package/build/angular/components/image/index.android.js +42 -10
- package/build/angular/components/image/index.android.js.map +1 -1
- package/build/angular/components/image/index.ios.d.ts +8 -3
- package/build/angular/components/image/index.ios.js +42 -10
- package/build/angular/components/image/index.ios.js.map +1 -1
- package/build/angular/components/image/shared.d.ts +4 -1
- package/build/angular/components/image/shared.js +51 -8
- package/build/angular/components/image/shared.js.map +1 -1
- package/build/angular/components/image-background.d.ts +2 -1
- package/build/angular/components/image-background.js +26 -10
- package/build/angular/components/image-background.js.map +1 -1
- package/build/angular/components/input-accessory-view/index.d.ts +9 -4
- package/build/angular/components/input-accessory-view/index.js +49 -10
- 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 +44 -12
- package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
- package/build/angular/components/modal/index.d.ts +11 -8
- package/build/angular/components/modal/index.js +51 -29
- package/build/angular/components/modal/index.js.map +1 -1
- package/build/angular/components/pressable/index.d.ts +12 -6
- package/build/angular/components/pressable/index.js +66 -13
- package/build/angular/components/pressable/index.js.map +1 -1
- package/build/angular/components/refresh-control/index.d.ts +8 -4
- package/build/angular/components/refresh-control/index.js +54 -16
- package/build/angular/components/refresh-control/index.js.map +1 -1
- package/build/angular/components/safe-area-view/index.d.ts +9 -4
- package/build/angular/components/safe-area-view/index.js +48 -9
- package/build/angular/components/safe-area-view/index.js.map +1 -1
- package/build/angular/components/scroll-view/index.android.d.ts +2 -3
- package/build/angular/components/scroll-view/index.android.js +88 -61
- package/build/angular/components/scroll-view/index.android.js.map +1 -1
- package/build/angular/components/scroll-view/index.ios.d.ts +2 -3
- package/build/angular/components/scroll-view/index.ios.js +24 -18
- package/build/angular/components/scroll-view/index.ios.js.map +1 -1
- package/build/angular/components/scroll-view/projection.d.ts +35 -0
- package/build/angular/components/scroll-view/projection.js +279 -53
- package/build/angular/components/scroll-view/projection.js.map +1 -1
- package/build/angular/components/scroll-view/shared.d.ts +14 -5
- package/build/angular/components/scroll-view/shared.js +225 -75
- package/build/angular/components/scroll-view/shared.js.map +1 -1
- package/build/angular/components/scroll-view/sticky-header.js +21 -22
- package/build/angular/components/scroll-view/sticky-header.js.map +1 -1
- package/build/angular/components/section-list/index.d.ts +7 -1
- package/build/angular/components/section-list/index.js +91 -54
- package/build/angular/components/section-list/index.js.map +1 -1
- package/build/angular/components/switch/index.android.d.ts +7 -3
- package/build/angular/components/switch/index.android.js +52 -10
- package/build/angular/components/switch/index.android.js.map +1 -1
- package/build/angular/components/switch/index.ios.d.ts +7 -3
- package/build/angular/components/switch/index.ios.js +52 -10
- package/build/angular/components/switch/index.ios.js.map +1 -1
- package/build/angular/components/switch/shared.d.ts +13 -4
- package/build/angular/components/switch/shared.js +71 -7
- package/build/angular/components/switch/shared.js.map +1 -1
- package/build/angular/components/text-input.d.ts +11 -3
- package/build/angular/components/text-input.js +68 -8
- package/build/angular/components/text-input.js.map +1 -1
- package/build/angular/components/touchable/index.d.ts +40 -9
- package/build/angular/components/touchable/index.js +303 -67
- package/build/angular/components/touchable/index.js.map +1 -1
- package/build/angular/components/touchable-native-feedback/index.d.ts +6 -3
- package/build/angular/components/touchable-native-feedback/index.js +63 -27
- package/build/angular/components/touchable-native-feedback/index.js.map +1 -1
- package/build/angular/components/virtualized-list/directives.js +22 -18
- package/build/angular/components/virtualized-list/directives.js.map +1 -1
- package/build/angular/components/virtualized-list/index.d.ts +15 -4
- package/build/angular/components/virtualized-list/index.js +326 -94
- package/build/angular/components/virtualized-list/index.js.map +1 -1
- package/build/angular/components/virtualized-section-list/directives.js +12 -12
- package/build/angular/components/virtualized-section-list/directives.js.map +1 -1
- package/build/angular/components/virtualized-section-list/index.d.ts +19 -1
- package/build/angular/components/virtualized-section-list/index.js +93 -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 +5 -0
- package/build/angular/components.js.map +1 -1
- package/build/angular/create-portal/index.js +34 -36
- package/build/angular/create-portal/index.js.map +1 -1
- package/build/angular/create-tunnel/index.js +19 -22
- package/build/angular/create-tunnel/index.js.map +1 -1
- package/build/angular/descriptor-to-angular/index.js +6 -4
- 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/host-instance/index.js +1 -1
- package/build/angular/host-instance/index.js.map +1 -1
- package/build/angular/index.d.ts +9 -4
- package/build/angular/index.js +17 -3
- package/build/angular/index.js.map +1 -1
- package/build/angular/modules/animated/animated-leaf-binder.d.ts +1 -7
- package/build/angular/modules/animated/animated-leaf-binder.js +22 -72
- package/build/angular/modules/animated/animated-leaf-binder.js.map +1 -1
- package/build/angular/modules/animated/create-animated-component.d.ts +11 -6
- package/build/angular/modules/animated/create-animated-component.js +106 -55
- package/build/angular/modules/animated/create-animated-component.js.map +1 -1
- package/build/angular/modules/status-bar/index.js +4 -4
- package/build/angular/modules/status-bar/index.js.map +1 -1
- package/build/angular/primitives/index.d.ts +20 -3
- package/build/angular/primitives/index.js +84 -48
- package/build/angular/primitives/index.js.map +1 -1
- package/build/angular/primitives/shared.d.ts +48 -2
- package/build/angular/primitives/shared.js +158 -11
- package/build/angular/primitives/shared.js.map +1 -1
- package/build/angular/render/index.js +69 -38
- package/build/angular/render/index.js.map +1 -1
- package/build/angular/renderer/index.d.ts +1 -0
- package/build/angular/renderer/index.js +37 -2
- package/build/angular/renderer/index.js.map +1 -1
- package/build/angular/services/color-scheme.service.js +3 -3
- package/build/angular/services/color-scheme.service.js.map +1 -1
- package/build/angular/services/window-dimensions.service.js +4 -4
- package/build/angular/services/window-dimensions.service.js.map +1 -1
- package/package.json +10 -8
- package/src/anchor-host-registry.ts +2 -2
- package/src/bootstrap.ts +4 -1
- package/src/components/activity-indicator/index.android.ts +8 -1
- package/src/components/activity-indicator/index.ios.ts +4 -0
- package/src/components/activity-indicator/shared.ts +32 -13
- package/src/components/button.ts +46 -19
- package/src/components/flat-list/index.ts +122 -62
- package/src/components/image/index.android.ts +58 -7
- package/src/components/image/index.ios.ts +58 -7
- package/src/components/image/shared.ts +92 -21
- package/src/components/image-background.ts +29 -9
- package/src/components/input-accessory-view/index.ts +67 -12
- package/src/components/keyboard-avoiding-view/index.ts +61 -15
- package/src/components/modal/index.ts +72 -33
- package/src/components/pressable/index.ts +117 -28
- package/src/components/refresh-control/index.ts +74 -18
- package/src/components/safe-area-view/index.ts +64 -10
- package/src/components/scroll-view/index.android.ts +62 -47
- package/src/components/scroll-view/index.ios.ts +19 -17
- package/src/components/scroll-view/projection.ts +363 -64
- package/src/components/scroll-view/shared.ts +312 -90
- package/src/components/scroll-view/sticky-header.ts +36 -24
- package/src/components/section-list/index.ts +86 -53
- package/src/components/switch/index.android.ts +60 -8
- package/src/components/switch/index.ios.ts +60 -8
- package/src/components/switch/shared.ts +93 -6
- package/src/components/text-input.ts +95 -13
- package/src/components/touchable/index.ts +348 -64
- package/src/components/touchable-native-feedback/index.ts +94 -31
- package/src/components/virtualized-list/directives.ts +19 -5
- package/src/components/virtualized-list/index.ts +380 -85
- package/src/components/virtualized-section-list/directives.ts +6 -3
- package/src/components/virtualized-section-list/index.ts +146 -33
- package/src/components.ts +9 -1
- package/src/create-portal/index.ts +31 -31
- package/src/create-tunnel/index.ts +16 -17
- package/src/descriptor-to-angular/index.ts +29 -8
- package/src/diagnostics.ts +138 -0
- package/src/host-instance/index.ts +9 -3
- package/src/index.ts +61 -3
- package/src/modules/animated/animated-leaf-binder.ts +28 -77
- package/src/modules/animated/create-animated-component.ts +115 -47
- package/src/modules/app-registry/index.ts +9 -9
- package/src/modules/status-bar/index.ts +13 -4
- package/src/primitives/index.ts +47 -2
- package/src/primitives/shared.ts +170 -10
- package/src/render/index.ts +77 -39
- package/src/renderer/index.ts +76 -14
- package/src/services/color-scheme.service.ts +3 -1
- package/src/services/window-dimensions.service.ts +11 -4
- package/build/angular/modules/animated/animated-leaf-binder/index.d.ts +0 -14
- package/build/angular/modules/animated/animated-leaf-binder/index.js +0 -91
- package/build/angular/modules/animated/animated-leaf-binder/index.js.map +0 -1
- package/src/modules/animated/animated-leaf-binder/index.ts +0 -101
|
@@ -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"`.
|
|
@@ -26,34 +27,49 @@ import {
|
|
|
26
27
|
CUSTOM_ELEMENTS_SCHEMA,
|
|
27
28
|
ChangeDetectionStrategy,
|
|
28
29
|
Component,
|
|
30
|
+
computed,
|
|
31
|
+
type DoCheck,
|
|
29
32
|
ElementRef,
|
|
30
33
|
EventEmitter,
|
|
31
34
|
inject,
|
|
32
35
|
Input,
|
|
36
|
+
type OnChanges,
|
|
37
|
+
type OnDestroy,
|
|
33
38
|
Output,
|
|
39
|
+
signal,
|
|
34
40
|
} from '@angular/core';
|
|
35
41
|
import {
|
|
42
|
+
createHighlightUnderlayHandlers,
|
|
43
|
+
createHighlightUnderlayRuntime,
|
|
36
44
|
createTouchableFeedbackHandlers,
|
|
37
45
|
createTouchableFeedbackRuntime,
|
|
38
|
-
|
|
46
|
+
hasTouchablePressHandler,
|
|
47
|
+
resolveHighlightExtraStyles,
|
|
48
|
+
restingOpacityFromStyle,
|
|
39
49
|
DEFAULT_ACTIVE_OPACITY,
|
|
40
|
-
|
|
41
|
-
DEFAULT_MIN_PRESS_DURATION_MS,
|
|
42
|
-
DEFAULT_UNDERLAY_COLOR,
|
|
43
|
-
OPACITY_ACTIVE_DURATION_MS,
|
|
50
|
+
OPACITY_ACTIVE_GRANT_DURATION_MS,
|
|
44
51
|
OPACITY_INACTIVE_DURATION_MS,
|
|
45
52
|
RESTING_OPACITY,
|
|
53
|
+
TOUCHABLE_MIN_PRESS_DURATION_MS,
|
|
46
54
|
type IAccessibilityProps,
|
|
47
55
|
type IAccessibilityStateValue,
|
|
48
56
|
type IAriaProps,
|
|
57
|
+
type IHighlightUnderlayHandlers,
|
|
49
58
|
type IPressableAndroidRippleConfig,
|
|
50
|
-
type IPressState,
|
|
51
59
|
type IPressTimingProps,
|
|
52
60
|
type IRectOffset,
|
|
53
61
|
type ITouchableFeedbackHandlers,
|
|
54
62
|
} from '@symbiote-native/components';
|
|
55
|
-
import {
|
|
56
|
-
|
|
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';
|
|
57
73
|
import { Pressable, type IAngularPressableInputs } from '../pressable';
|
|
58
74
|
import { Animated, AnimatedView } from '../../modules/animated';
|
|
59
75
|
|
|
@@ -78,16 +94,43 @@ export type IAngularTouchableHighlightProps = IAngularTouchableBaseProps & {
|
|
|
78
94
|
|
|
79
95
|
export type IAngularTouchableWithoutFeedbackProps = IAngularTouchableBaseProps;
|
|
80
96
|
|
|
81
|
-
// The real
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
97
|
+
// The real timers both shared machines schedule on — core/components carries no timer globals, so
|
|
98
|
+
// scheduling is the adapter's half. Every canceller is retained so ngOnDestroy cancels what is
|
|
99
|
+
// still in flight: a deferred press-out or a held underlay firing after teardown would emit from a
|
|
100
|
+
// destroyed component and write a signal on a destroyed view.
|
|
101
|
+
interface ITimerScheduler {
|
|
102
|
+
schedule: (callback: () => void, ms: number) => () => void;
|
|
103
|
+
cancelAll: () => void;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function createTimerScheduler(): ITimerScheduler {
|
|
107
|
+
const pending = new Set<() => void>();
|
|
108
|
+
return {
|
|
109
|
+
schedule(callback: () => void, ms: number): () => void {
|
|
110
|
+
const id = setTimeout(() => {
|
|
111
|
+
pending.delete(cancel);
|
|
112
|
+
callback();
|
|
113
|
+
}, ms);
|
|
114
|
+
const cancel = (): void => {
|
|
115
|
+
clearTimeout(id);
|
|
116
|
+
pending.delete(cancel);
|
|
117
|
+
};
|
|
118
|
+
pending.add(cancel);
|
|
119
|
+
return cancel;
|
|
120
|
+
},
|
|
121
|
+
cancelAll(): void {
|
|
122
|
+
for (const cancel of [...pending]) cancel();
|
|
123
|
+
pending.clear();
|
|
124
|
+
},
|
|
125
|
+
};
|
|
86
126
|
}
|
|
87
127
|
|
|
88
128
|
@Component({
|
|
89
129
|
selector: 'TouchableOpacity',
|
|
90
130
|
standalone: true,
|
|
131
|
+
hostDirectives: [
|
|
132
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
133
|
+
],
|
|
91
134
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
92
135
|
imports: [Pressable, AnimatedView],
|
|
93
136
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -133,8 +176,12 @@ function scheduleTimeout(callback: () => void, ms: number): () => void {
|
|
|
133
176
|
[accessibilityElementsHidden]="accessibilityElementsHidden"
|
|
134
177
|
[accessibilityIgnoresInvertColors]="accessibilityIgnoresInvertColors"
|
|
135
178
|
[accessibilityLanguage]="accessibilityLanguage"
|
|
136
|
-
[accessibilityRespondsToUserInteraction]="
|
|
137
|
-
|
|
179
|
+
[accessibilityRespondsToUserInteraction]="
|
|
180
|
+
accessibilityRespondsToUserInteraction
|
|
181
|
+
"
|
|
182
|
+
[accessibilityShowsLargeContentViewer]="
|
|
183
|
+
accessibilityShowsLargeContentViewer
|
|
184
|
+
"
|
|
138
185
|
[accessibilityLargeContentTitle]="accessibilityLargeContentTitle"
|
|
139
186
|
(accessibilityAction)="accessibilityAction.emit($event)"
|
|
140
187
|
(accessibilityTap)="accessibilityTap.emit($event)"
|
|
@@ -163,7 +210,9 @@ function scheduleTimeout(callback: () => void, ms: number): () => void {
|
|
|
163
210
|
</Pressable>
|
|
164
211
|
`,
|
|
165
212
|
})
|
|
166
|
-
export class TouchableOpacity
|
|
213
|
+
export class TouchableOpacity
|
|
214
|
+
implements IAngularTouchableOpacityProps, OnChanges, OnDestroy, DoCheck
|
|
215
|
+
{
|
|
167
216
|
@Output() readonly press = new EventEmitter<ISymbioteEvent>();
|
|
168
217
|
@Output() readonly pressIn = new EventEmitter<ISymbioteEvent>();
|
|
169
218
|
@Output() readonly pressOut = new EventEmitter<ISymbioteEvent>();
|
|
@@ -206,8 +255,10 @@ export class TouchableOpacity implements IAngularTouchableOpacityProps {
|
|
|
206
255
|
@Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
|
|
207
256
|
@Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
|
|
208
257
|
@Input() accessibilityLabelledBy?: string | string[];
|
|
209
|
-
@Input()
|
|
210
|
-
|
|
258
|
+
@Input()
|
|
259
|
+
importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
|
|
260
|
+
@Input()
|
|
261
|
+
accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
|
|
211
262
|
@Input() screenReaderFocusable?: boolean;
|
|
212
263
|
@Input() accessibilityViewIsModal?: boolean;
|
|
213
264
|
@Input() accessibilityElementsHidden?: boolean;
|
|
@@ -233,13 +284,21 @@ export class TouchableOpacity implements IAngularTouchableOpacityProps {
|
|
|
233
284
|
@Input() id?: string;
|
|
234
285
|
@Input() role?: IAriaProps['role'];
|
|
235
286
|
|
|
236
|
-
// One Animated.Value per mount
|
|
237
|
-
//
|
|
238
|
-
//
|
|
287
|
+
// One Animated.Value per mount. Held by IDENTITY as a plain field (an engine object, never an
|
|
288
|
+
// @Input / reactive wrap); the <symbiote-animated-view> leaf rasterizes it for the first paint
|
|
289
|
+
// and drives it through setNativeProps every frame.
|
|
290
|
+
//
|
|
291
|
+
// RN seeds it from _getChildStyleOpacityWithDefault(props.style). Angular writes @Input()s AFTER
|
|
292
|
+
// the field initializers run, so `style` is still undefined here — the real resting value is
|
|
293
|
+
// seeded by the first ngOnChanges below, which still precedes the first template pass.
|
|
239
294
|
private readonly opacity = new Animated.Value(RESTING_OPACITY);
|
|
240
295
|
// The shared press-scheduling cell (delayPressIn timer + activation clock), persisted on the
|
|
241
296
|
// instance; the machine's handlers are rebuilt per event over live @Input()s.
|
|
242
297
|
private readonly runtime = createTouchableFeedbackRuntime();
|
|
298
|
+
private readonly timers = createTimerScheduler();
|
|
299
|
+
// What the last settle ran for. Undefined until the first ngOnChanges, which is the MOUNT.
|
|
300
|
+
private settled:
|
|
301
|
+
{ disabled: boolean | undefined; resting: number } | undefined;
|
|
243
302
|
// This component's OWN host — the non-painting anchor `class="..."` at the use site resolves
|
|
244
303
|
// onto (see anchorHostStyle's doc comment) — NOT the <symbiote-animated-view> leaf one level down.
|
|
245
304
|
private readonly elementRef = inject(ElementRef);
|
|
@@ -249,7 +308,53 @@ export class TouchableOpacity implements IAngularTouchableOpacityProps {
|
|
|
249
308
|
// the commit layer flattens). `unknown` because the array mixes a plain style with an
|
|
250
309
|
// AnimatedNode, exactly the bag AnimatedView's `style` input accepts.
|
|
251
310
|
get animatedStyle(): unknown {
|
|
252
|
-
return [
|
|
311
|
+
return [this.anchorStyle(), this.style, { opacity: this.opacity }];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// The anchor's class-derived style is written by the renderer's addClass/removeClass at the USE
|
|
315
|
+
// SITE - it never appears in SimpleChanges, and nothing about it dirties THIS component's view.
|
|
316
|
+
// A `class=` present at creation therefore worked, while one toggled later did not: the parent
|
|
317
|
+
// refreshed, the class landed on the anchor, and this component's own view was never refreshed,
|
|
318
|
+
// so whatever merged the anchor style in was never re-evaluated. Polling it into a signal from
|
|
319
|
+
// ngDoCheck fixes both halves: ngDoCheck runs during the PARENT's refresh even when this view is
|
|
320
|
+
// skipped, and the signal write is what then marks this view for refresh. `signal.set`'s own
|
|
321
|
+
// Object.is makes an unchanged poll a no-op, so there is no loop.
|
|
322
|
+
private readonly anchorStyle = signal<unknown>(undefined);
|
|
323
|
+
|
|
324
|
+
ngDoCheck(): void {
|
|
325
|
+
this.anchorStyle.set(anchorHostStyle(this.elementRef));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// RN's componentDidUpdate: a changed `disabled` or a changed style opacity re-settles the view,
|
|
329
|
+
// so a Touchable disabled mid-press does not stay stuck at its active opacity. The FIRST call is
|
|
330
|
+
// the mount (ngOnChanges runs before ngOnInit and before the first template pass), so it seeds
|
|
331
|
+
// the Value instead of animating — RN does this on update only, and a fade at mount would animate
|
|
332
|
+
// away from the value the leaf is about to paint. A use site with no bindings gets no ngOnChanges
|
|
333
|
+
// at all, and there the RESTING_OPACITY seed is already the right answer.
|
|
334
|
+
ngOnChanges(): void {
|
|
335
|
+
const settled = { disabled: this.disabled, resting: this.restingOpacity() };
|
|
336
|
+
if (this.settled === undefined) {
|
|
337
|
+
this.opacity.setValue(settled.resting);
|
|
338
|
+
} else if (
|
|
339
|
+
this.settled.disabled !== settled.disabled ||
|
|
340
|
+
this.settled.resting !== settled.resting
|
|
341
|
+
) {
|
|
342
|
+
this.setOpacityTo(settled.resting, OPACITY_INACTIVE_DURATION_MS);
|
|
343
|
+
}
|
|
344
|
+
this.settled = settled;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// RN's componentWillUnmount: stop the animation so a teardown mid-fade leaves no driver running
|
|
348
|
+
// against a node that is gone, and drop any press-out still deferred behind a timer.
|
|
349
|
+
ngOnDestroy(): void {
|
|
350
|
+
this.timers.cancelAll();
|
|
351
|
+
this.opacity.resetAnimation();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// RN's _getChildStyleOpacityWithDefault: the fade settles at the opacity the CALLER's style asks
|
|
355
|
+
// for, not at a hard 1 — a Touchable styled `opacity: 0.6` must come back to 0.6.
|
|
356
|
+
private restingOpacity(): number {
|
|
357
|
+
return restingOpacityFromStyle(this.style);
|
|
253
358
|
}
|
|
254
359
|
|
|
255
360
|
private setOpacityTo(toValue: number, duration: number): void {
|
|
@@ -257,7 +362,9 @@ export class TouchableOpacity implements IAngularTouchableOpacityProps {
|
|
|
257
362
|
toValue,
|
|
258
363
|
duration,
|
|
259
364
|
easing: Animated.Easing.inOut(Animated.Easing.quad),
|
|
260
|
-
|
|
365
|
+
// RN's own (TouchableOpacity.js:242). opacity is natively drivable, so the fade survives a
|
|
366
|
+
// busy JS thread — which is the entire point of press feedback.
|
|
367
|
+
useNativeDriver: true,
|
|
261
368
|
}).start();
|
|
262
369
|
}
|
|
263
370
|
|
|
@@ -269,8 +376,11 @@ export class TouchableOpacity implements IAngularTouchableOpacityProps {
|
|
|
269
376
|
{
|
|
270
377
|
delayPressIn: this.delayPressIn ?? 0,
|
|
271
378
|
delayPressOut: this.delayPressOut ?? 0,
|
|
272
|
-
|
|
273
|
-
|
|
379
|
+
// RN's Touchables override Pressability's own 130ms floor with 0; what holds the active
|
|
380
|
+
// visual there is the fade's own duration, not a press-duration floor.
|
|
381
|
+
minPressDuration:
|
|
382
|
+
this.minPressDuration ?? TOUCHABLE_MIN_PRESS_DURATION_MS,
|
|
383
|
+
schedule: this.timers.schedule,
|
|
274
384
|
now: Date.now,
|
|
275
385
|
},
|
|
276
386
|
this.runtime,
|
|
@@ -278,12 +388,15 @@ export class TouchableOpacity implements IAngularTouchableOpacityProps {
|
|
|
278
388
|
activate: (event: ISymbioteEvent): void => {
|
|
279
389
|
this.setOpacityTo(
|
|
280
390
|
this.activeOpacity ?? DEFAULT_ACTIVE_OPACITY,
|
|
281
|
-
|
|
391
|
+
OPACITY_ACTIVE_GRANT_DURATION_MS,
|
|
282
392
|
);
|
|
283
393
|
this.pressIn.emit(event);
|
|
284
394
|
},
|
|
285
395
|
deactivate: (event: ISymbioteEvent): void => {
|
|
286
|
-
this.setOpacityTo(
|
|
396
|
+
this.setOpacityTo(
|
|
397
|
+
this.restingOpacity(),
|
|
398
|
+
OPACITY_INACTIVE_DURATION_MS,
|
|
399
|
+
);
|
|
287
400
|
this.pressOut.emit(event);
|
|
288
401
|
},
|
|
289
402
|
},
|
|
@@ -304,14 +417,17 @@ export class TouchableOpacity implements IAngularTouchableOpacityProps {
|
|
|
304
417
|
@Component({
|
|
305
418
|
selector: 'TouchableHighlight',
|
|
306
419
|
standalone: true,
|
|
420
|
+
hostDirectives: [
|
|
421
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
422
|
+
],
|
|
307
423
|
imports: [Pressable],
|
|
308
424
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
309
425
|
template: `
|
|
310
426
|
<Pressable
|
|
311
|
-
[style]="
|
|
312
|
-
(press)="
|
|
313
|
-
(pressIn)="
|
|
314
|
-
(pressOut)="
|
|
427
|
+
[style]="containerStyle()"
|
|
428
|
+
(press)="handlePress($event)"
|
|
429
|
+
(pressIn)="handlePressIn($event)"
|
|
430
|
+
(pressOut)="handlePressOut($event)"
|
|
315
431
|
(pressMove)="pressMove.emit($event)"
|
|
316
432
|
(longPress)="longPress.emit($event)"
|
|
317
433
|
(hoverIn)="hoverIn.emit($event)"
|
|
@@ -349,8 +465,12 @@ export class TouchableOpacity implements IAngularTouchableOpacityProps {
|
|
|
349
465
|
[accessibilityElementsHidden]="accessibilityElementsHidden"
|
|
350
466
|
[accessibilityIgnoresInvertColors]="accessibilityIgnoresInvertColors"
|
|
351
467
|
[accessibilityLanguage]="accessibilityLanguage"
|
|
352
|
-
[accessibilityRespondsToUserInteraction]="
|
|
353
|
-
|
|
468
|
+
[accessibilityRespondsToUserInteraction]="
|
|
469
|
+
accessibilityRespondsToUserInteraction
|
|
470
|
+
"
|
|
471
|
+
[accessibilityShowsLargeContentViewer]="
|
|
472
|
+
accessibilityShowsLargeContentViewer
|
|
473
|
+
"
|
|
354
474
|
[accessibilityLargeContentTitle]="accessibilityLargeContentTitle"
|
|
355
475
|
(accessibilityAction)="accessibilityAction.emit($event)"
|
|
356
476
|
(accessibilityTap)="accessibilityTap.emit($event)"
|
|
@@ -377,7 +497,9 @@ export class TouchableOpacity implements IAngularTouchableOpacityProps {
|
|
|
377
497
|
</Pressable>
|
|
378
498
|
`,
|
|
379
499
|
})
|
|
380
|
-
export class TouchableHighlight
|
|
500
|
+
export class TouchableHighlight
|
|
501
|
+
implements IAngularTouchableHighlightProps, OnChanges, OnDestroy, DoCheck
|
|
502
|
+
{
|
|
381
503
|
@Output() readonly press = new EventEmitter<ISymbioteEvent>();
|
|
382
504
|
@Output() readonly pressIn = new EventEmitter<ISymbioteEvent>();
|
|
383
505
|
@Output() readonly pressOut = new EventEmitter<ISymbioteEvent>();
|
|
@@ -389,6 +511,9 @@ export class TouchableHighlight implements IAngularTouchableHighlightProps {
|
|
|
389
511
|
@Output() readonly accessibilityTap = new EventEmitter<ISymbioteEvent>();
|
|
390
512
|
@Output() readonly magicTap = new EventEmitter<ISymbioteEvent>();
|
|
391
513
|
@Output() readonly accessibilityEscape = new EventEmitter<ISymbioteEvent>();
|
|
514
|
+
// RN's onShowUnderlay / onHideUnderlay — fired on a real transition only, never on a repeat.
|
|
515
|
+
@Output() readonly showUnderlay = new EventEmitter<void>();
|
|
516
|
+
@Output() readonly hideUnderlay = new EventEmitter<void>();
|
|
392
517
|
@Input() activeOpacity?: number;
|
|
393
518
|
@Input() underlayColor?: string;
|
|
394
519
|
@Input() delayPressIn?: number;
|
|
@@ -421,8 +546,10 @@ export class TouchableHighlight implements IAngularTouchableHighlightProps {
|
|
|
421
546
|
@Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
|
|
422
547
|
@Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
|
|
423
548
|
@Input() accessibilityLabelledBy?: string | string[];
|
|
424
|
-
@Input()
|
|
425
|
-
|
|
549
|
+
@Input()
|
|
550
|
+
importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
|
|
551
|
+
@Input()
|
|
552
|
+
accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
|
|
426
553
|
@Input() screenReaderFocusable?: boolean;
|
|
427
554
|
@Input() accessibilityViewIsModal?: boolean;
|
|
428
555
|
@Input() accessibilityElementsHidden?: boolean;
|
|
@@ -452,34 +579,131 @@ export class TouchableHighlight implements IAngularTouchableHighlightProps {
|
|
|
452
579
|
// onto (see anchorHostStyle's doc comment) — NOT the Pressable one level down.
|
|
453
580
|
private readonly elementRef = inject(ElementRef);
|
|
454
581
|
|
|
455
|
-
//
|
|
456
|
-
//
|
|
457
|
-
//
|
|
458
|
-
//
|
|
459
|
-
//
|
|
460
|
-
//
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
582
|
+
// `shown` is NOT Pressable's `pressed`. RN holds the underlay for delayPressOut past the tap so a
|
|
583
|
+
// press too fast to see still flashes, and the pressed flag is already false by then. A SIGNAL,
|
|
584
|
+
// because the hide fires from a timer — outside any Angular listener, so under zoneless CD
|
|
585
|
+
// nothing else would mark this view. A signal write both notifies the change-detection scheduler
|
|
586
|
+
// (markAncestorsForTraversal) and dirties this template's own reactive consumer, which is the one
|
|
587
|
+
// thing honored in Targeted mode; markForCheck from a plain field would not be reachable here.
|
|
588
|
+
private readonly shown = signal(false);
|
|
589
|
+
private readonly underlayRuntime = createHighlightUnderlayRuntime();
|
|
590
|
+
private readonly timers = createTimerScheduler();
|
|
591
|
+
|
|
592
|
+
// The container style Pressable paints.
|
|
593
|
+
//
|
|
594
|
+
// POINT-7 DECISION — both halves stay on the CONTAINER, deliberately. RN's _createExtraStyles
|
|
595
|
+
// keeps them apart: `underlay` (the backgroundColor) on the container, `child` (the lowered
|
|
596
|
+
// opacity) cloned onto the single child. Angular has no cloneElement and no way to reach a
|
|
597
|
+
// projected <ng-content> node, so the only path to the child is a permanent wrapper View — which
|
|
598
|
+
// inserts a node into the flex chain and silently re-parents any `flex` a child declares. The
|
|
599
|
+
// fake Fabric runs no Yoga, so no headless test can measure that damage, only assert the extra
|
|
600
|
+
// node exists; shipping an unmeasurable layout change is worse than the visual approximation.
|
|
601
|
+
// Same call, same reason, as the Solid adapter. resolveHighlightExtraStyles keeps the two styles
|
|
602
|
+
// SEPARATE regardless, so a layout-safe fix (which needs a device check, not a test) stays open.
|
|
603
|
+
//
|
|
604
|
+
// Returns a NEW ARRAY whenever a dependency moves: Pressable's `style` @Input then reports an
|
|
605
|
+
// ordinary change. A stable reference was the earlier bug — Pressable's view never refreshed, so
|
|
606
|
+
// a class or [style] toggled after mount never reached the committed view.
|
|
607
|
+
readonly containerStyle = computed<IStyleProp<IViewStyle>>(() => {
|
|
608
|
+
this.inputsRevision();
|
|
609
|
+
const base: IStyleProp<IViewStyle> = [this.anchorStyle(), this.style];
|
|
610
|
+
const extra = resolveHighlightExtraStyles({
|
|
611
|
+
shown: this.shown(),
|
|
612
|
+
hasPressHandler: this.hasPressHandler(),
|
|
613
|
+
underlayColor: this.underlayColor,
|
|
614
|
+
activeOpacity: this.activeOpacity,
|
|
615
|
+
});
|
|
616
|
+
return extra === undefined ? base : [base, extra.underlay, extra.child];
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
// RN's _hasPressHandler: a Touchable that handles no press paints no underlay. Angular exposes
|
|
620
|
+
// the press lifecycle as @Output()s, so "a handler is present" is "something is subscribed" — the
|
|
621
|
+
// same `.observed` test Pressable uses to decide whether to arm its long-press timer at all.
|
|
622
|
+
private hasPressHandler(): boolean {
|
|
623
|
+
return hasTouchablePressHandler({
|
|
624
|
+
onPress: this.press.observed ? this.press : undefined,
|
|
625
|
+
onPressIn: this.pressIn.observed ? this.pressIn : undefined,
|
|
626
|
+
onPressOut: this.pressOut.observed ? this.pressOut : undefined,
|
|
627
|
+
onLongPress: this.longPress.observed ? this.longPress : undefined,
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Built per event so the machine reads live @Input()s (delayPressOut, the has-handler gate); the
|
|
632
|
+
// runtime persists across calls, which is what lets handlePressOut see the timer handlePress set.
|
|
633
|
+
private underlayHandlers(): IHighlightUnderlayHandlers {
|
|
634
|
+
return createHighlightUnderlayHandlers(
|
|
635
|
+
{
|
|
636
|
+
delayPressOut: this.delayPressOut ?? 0,
|
|
637
|
+
hasPressHandler: this.hasPressHandler(),
|
|
638
|
+
schedule: this.timers.schedule,
|
|
639
|
+
},
|
|
640
|
+
this.underlayRuntime,
|
|
641
|
+
{
|
|
642
|
+
setShown: (shown: boolean): void => this.shown.set(shown),
|
|
643
|
+
onShowUnderlay: (): void => this.showUnderlay.emit(),
|
|
644
|
+
onHideUnderlay: (): void => this.hideUnderlay.emit(),
|
|
645
|
+
},
|
|
468
646
|
);
|
|
469
|
-
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Visual first, then the caller's own event — RN's order in _createPressabilityConfig.
|
|
650
|
+
// onLongPress is deliberately NOT intercepted (Pressable emits it straight through); it is only
|
|
651
|
+
// READ, by the has-press-handler gate above.
|
|
652
|
+
handlePressIn(event: ISymbioteEvent): void {
|
|
653
|
+
this.underlayHandlers().handlePressIn(event);
|
|
654
|
+
this.pressIn.emit(event);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
handlePress(event: ISymbioteEvent): void {
|
|
658
|
+
this.underlayHandlers().handlePress(event);
|
|
659
|
+
this.press.emit(event);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
handlePressOut(event: ISymbioteEvent): void {
|
|
663
|
+
this.underlayHandlers().handlePressOut(event);
|
|
664
|
+
this.pressOut.emit(event);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
ngOnDestroy(): void {
|
|
668
|
+
this.timers.cancelAll();
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// `style`, `underlayColor` and `activeOpacity` are read untracked inside containerStyle, so the
|
|
672
|
+
// bump is what tells it they moved. See the ngDoCheck note below for the anchor half, which
|
|
673
|
+
// ngOnChanges cannot cover.
|
|
674
|
+
private readonly inputsRevision = signal(0);
|
|
675
|
+
private readonly anchorStyle = signal<IStyleProp<IViewStyle> | undefined>(
|
|
676
|
+
undefined,
|
|
677
|
+
);
|
|
678
|
+
|
|
679
|
+
ngOnChanges(): void {
|
|
680
|
+
this.inputsRevision.update(revision => revision + 1);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// The anchor's class-derived style is written by the renderer's addClass/removeClass at the USE
|
|
684
|
+
// SITE - it never appears in SimpleChanges, and nothing about it dirties THIS component's view.
|
|
685
|
+
// ngDoCheck runs during the PARENT's refresh even when this view is skipped, and the signal
|
|
686
|
+
// write is what then marks this view for refresh. `signal.set`'s own Object.is makes an
|
|
687
|
+
// unchanged poll a no-op, so there is no loop.
|
|
688
|
+
ngDoCheck(): void {
|
|
689
|
+
this.anchorStyle.set(anchorStyleProp<IViewStyle>(this.elementRef));
|
|
690
|
+
}
|
|
470
691
|
}
|
|
471
692
|
|
|
472
693
|
@Component({
|
|
473
694
|
selector: 'TouchableWithoutFeedback',
|
|
474
695
|
standalone: true,
|
|
696
|
+
hostDirectives: [
|
|
697
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
698
|
+
],
|
|
475
699
|
imports: [Pressable],
|
|
476
700
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
477
701
|
template: `
|
|
478
702
|
<Pressable
|
|
479
703
|
[style]="mergedStyle"
|
|
480
704
|
(press)="press.emit($event)"
|
|
481
|
-
(pressIn)="
|
|
482
|
-
(pressOut)="
|
|
705
|
+
(pressIn)="handlePressIn($event)"
|
|
706
|
+
(pressOut)="handlePressOut($event)"
|
|
483
707
|
(pressMove)="pressMove.emit($event)"
|
|
484
708
|
(longPress)="longPress.emit($event)"
|
|
485
709
|
(hoverIn)="hoverIn.emit($event)"
|
|
@@ -517,8 +741,12 @@ export class TouchableHighlight implements IAngularTouchableHighlightProps {
|
|
|
517
741
|
[accessibilityElementsHidden]="accessibilityElementsHidden"
|
|
518
742
|
[accessibilityIgnoresInvertColors]="accessibilityIgnoresInvertColors"
|
|
519
743
|
[accessibilityLanguage]="accessibilityLanguage"
|
|
520
|
-
[accessibilityRespondsToUserInteraction]="
|
|
521
|
-
|
|
744
|
+
[accessibilityRespondsToUserInteraction]="
|
|
745
|
+
accessibilityRespondsToUserInteraction
|
|
746
|
+
"
|
|
747
|
+
[accessibilityShowsLargeContentViewer]="
|
|
748
|
+
accessibilityShowsLargeContentViewer
|
|
749
|
+
"
|
|
522
750
|
[accessibilityLargeContentTitle]="accessibilityLargeContentTitle"
|
|
523
751
|
(accessibilityAction)="accessibilityAction.emit($event)"
|
|
524
752
|
(accessibilityTap)="accessibilityTap.emit($event)"
|
|
@@ -545,10 +773,13 @@ export class TouchableHighlight implements IAngularTouchableHighlightProps {
|
|
|
545
773
|
</Pressable>
|
|
546
774
|
`,
|
|
547
775
|
})
|
|
548
|
-
export class TouchableWithoutFeedback
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
//
|
|
776
|
+
export class TouchableWithoutFeedback
|
|
777
|
+
implements DoCheck, OnDestroy, IAngularTouchableWithoutFeedbackProps
|
|
778
|
+
{
|
|
779
|
+
// RN's TouchableWithoutFeedback builds a FULL Pressability config with delayPressIn /
|
|
780
|
+
// delayPressOut / minPressDuration: 0 — "without feedback" means no VISUAL, not no timing. So the
|
|
781
|
+
// same shared machine TouchableOpacity uses runs here, with the visual half left empty: the
|
|
782
|
+
// pressIn/pressOut EMIT is what gets deferred and floored.
|
|
552
783
|
@Output() readonly press = new EventEmitter<ISymbioteEvent>();
|
|
553
784
|
@Output() readonly pressIn = new EventEmitter<ISymbioteEvent>();
|
|
554
785
|
@Output() readonly pressOut = new EventEmitter<ISymbioteEvent>();
|
|
@@ -590,8 +821,10 @@ export class TouchableWithoutFeedback implements IAngularTouchableWithoutFeedbac
|
|
|
590
821
|
@Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
|
|
591
822
|
@Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
|
|
592
823
|
@Input() accessibilityLabelledBy?: string | string[];
|
|
593
|
-
@Input()
|
|
594
|
-
|
|
824
|
+
@Input()
|
|
825
|
+
importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
|
|
826
|
+
@Input()
|
|
827
|
+
accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
|
|
595
828
|
@Input() screenReaderFocusable?: boolean;
|
|
596
829
|
@Input() accessibilityViewIsModal?: boolean;
|
|
597
830
|
@Input() accessibilityElementsHidden?: boolean;
|
|
@@ -621,9 +854,60 @@ export class TouchableWithoutFeedback implements IAngularTouchableWithoutFeedbac
|
|
|
621
854
|
// onto (see anchorHostStyle's doc comment) — NOT the Pressable one level down.
|
|
622
855
|
private readonly elementRef = inject(ElementRef);
|
|
623
856
|
|
|
857
|
+
// The shared press-scheduling cell (delayPressIn timer + activation clock), persisted on the
|
|
858
|
+
// instance; the machine's handlers are rebuilt per event over live @Input()s.
|
|
859
|
+
private readonly runtime = createTouchableFeedbackRuntime();
|
|
860
|
+
private readonly timers = createTimerScheduler();
|
|
861
|
+
|
|
624
862
|
// The anchor's class-derived style goes first, then the explicit style, so an explicit [style]
|
|
625
863
|
// still beats the ambient class.
|
|
626
864
|
get mergedStyle(): IStyleProp<IViewStyle> {
|
|
627
|
-
return [
|
|
865
|
+
return [this.anchorStyle(), this.style];
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
private feedbackHandlers(): ITouchableFeedbackHandlers {
|
|
869
|
+
return createTouchableFeedbackHandlers(
|
|
870
|
+
{
|
|
871
|
+
delayPressIn: this.delayPressIn ?? 0,
|
|
872
|
+
delayPressOut: this.delayPressOut ?? 0,
|
|
873
|
+
minPressDuration:
|
|
874
|
+
this.minPressDuration ?? TOUCHABLE_MIN_PRESS_DURATION_MS,
|
|
875
|
+
schedule: this.timers.schedule,
|
|
876
|
+
now: Date.now,
|
|
877
|
+
},
|
|
878
|
+
this.runtime,
|
|
879
|
+
{
|
|
880
|
+
activate: (event: ISymbioteEvent): void => this.pressIn.emit(event),
|
|
881
|
+
deactivate: (event: ISymbioteEvent): void => this.pressOut.emit(event),
|
|
882
|
+
},
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
handlePressIn(event: ISymbioteEvent): void {
|
|
887
|
+
this.feedbackHandlers().handlePressIn(event);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
handlePressOut(event: ISymbioteEvent): void {
|
|
891
|
+
this.feedbackHandlers().handlePressOut(event);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
ngOnDestroy(): void {
|
|
895
|
+
this.timers.cancelAll();
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// The anchor's class-derived style is written by the renderer's addClass/removeClass at the USE
|
|
899
|
+
// SITE - it never appears in SimpleChanges, and nothing about it dirties THIS component's view.
|
|
900
|
+
// A `class=` present at creation therefore worked, while one toggled later did not: the parent
|
|
901
|
+
// refreshed, the class landed on the anchor, and this component's own view was never refreshed,
|
|
902
|
+
// so whatever merged the anchor style in was never re-evaluated. Polling it into a signal from
|
|
903
|
+
// ngDoCheck fixes both halves: ngDoCheck runs during the PARENT's refresh even when this view is
|
|
904
|
+
// skipped, and the signal write is what then marks this view for refresh. `signal.set`'s own
|
|
905
|
+
// Object.is makes an unchanged poll a no-op, so there is no loop.
|
|
906
|
+
private readonly anchorStyle = signal<IStyleProp<IViewStyle> | undefined>(
|
|
907
|
+
undefined,
|
|
908
|
+
);
|
|
909
|
+
|
|
910
|
+
ngDoCheck(): void {
|
|
911
|
+
this.anchorStyle.set(anchorStyleProp<IViewStyle>(this.elementRef));
|
|
628
912
|
}
|
|
629
913
|
}
|