@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
|
@@ -101,7 +101,9 @@ export type IAngularModalInputs = Omit<
|
|
|
101
101
|
@Component({
|
|
102
102
|
selector: 'Modal',
|
|
103
103
|
standalone: true,
|
|
104
|
-
hostDirectives: [
|
|
104
|
+
hostDirectives: [
|
|
105
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
106
|
+
],
|
|
105
107
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
106
108
|
imports: [ModalHost, ViewHost, SymbioteHostPropsDirective],
|
|
107
109
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -113,10 +115,6 @@ export type IAngularModalInputs = Omit<
|
|
|
113
115
|
(dismiss)="dismiss.emit()"
|
|
114
116
|
(requestClose)="requestClose.emit()"
|
|
115
117
|
(orientationChange)="emit(orientationChange, $event)"
|
|
116
|
-
(accessibilityAction)="emit(accessibilityAction, $event)"
|
|
117
|
-
(accessibilityTap)="emit(accessibilityTap, $event)"
|
|
118
|
-
(magicTap)="emit(magicTap, $event)"
|
|
119
|
-
(accessibilityEscape)="emit(accessibilityEscape, $event)"
|
|
120
118
|
>
|
|
121
119
|
<symbiote-view [style]="containerStyle" [collapsable]="false">
|
|
122
120
|
<ng-content></ng-content>
|
|
@@ -154,8 +152,10 @@ export class Modal implements IAngularModalInputs, OnInit, OnChanges, DoCheck {
|
|
|
154
152
|
@Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
|
|
155
153
|
@Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
|
|
156
154
|
@Input() accessibilityLabelledBy?: string | string[];
|
|
157
|
-
@Input()
|
|
158
|
-
|
|
155
|
+
@Input()
|
|
156
|
+
importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
|
|
157
|
+
@Input()
|
|
158
|
+
accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
|
|
159
159
|
@Input() screenReaderFocusable?: boolean;
|
|
160
160
|
@Input() accessibilityViewIsModal?: boolean;
|
|
161
161
|
@Input() accessibilityElementsHidden?: boolean;
|
|
@@ -216,7 +216,10 @@ export class Modal implements IAngularModalInputs, OnInit, OnChanges, DoCheck {
|
|
|
216
216
|
// Queue on a microtask so the reducer runs AFTER this CD pass renders with the OLD state —
|
|
217
217
|
// the keep-alive frame. A synchronous dispatch here would unmount in the same pass.
|
|
218
218
|
queueMicrotask(() => {
|
|
219
|
-
this.state = modalReducer(
|
|
219
|
+
this.state = modalReducer(
|
|
220
|
+
this.state,
|
|
221
|
+
isVisible ? { type: 'show' } : { type: 'hide' },
|
|
222
|
+
);
|
|
220
223
|
this.changeDetector.markForCheck();
|
|
221
224
|
});
|
|
222
225
|
}
|
|
@@ -269,7 +272,14 @@ export class Modal implements IAngularModalInputs, OnInit, OnChanges, DoCheck {
|
|
|
269
272
|
readonly hostProps = computed<Record<string, unknown>>(() => {
|
|
270
273
|
this.hostPropsRevision();
|
|
271
274
|
const descriptorProps = this.descriptor.props;
|
|
272
|
-
return {
|
|
275
|
+
return {
|
|
276
|
+
...descriptorProps,
|
|
277
|
+
style: [anchorHostStyle(this.elementRef), descriptorProps.style],
|
|
278
|
+
onAccessibilityAction: this.eventEmitterHandler(this.accessibilityAction),
|
|
279
|
+
onAccessibilityTap: this.eventEmitterHandler(this.accessibilityTap),
|
|
280
|
+
onMagicTap: this.eventEmitterHandler(this.magicTap),
|
|
281
|
+
onAccessibilityEscape: this.eventEmitterHandler(this.accessibilityEscape),
|
|
282
|
+
};
|
|
273
283
|
});
|
|
274
284
|
|
|
275
285
|
get containerStyle(): unknown {
|
|
@@ -277,13 +287,28 @@ export class Modal implements IAngularModalInputs, OnInit, OnChanges, DoCheck {
|
|
|
277
287
|
return typeof container === 'string' ? undefined : container.props.style;
|
|
278
288
|
}
|
|
279
289
|
|
|
290
|
+
// NOT private: `(orientationChange)="emit(...)"` in the template above calls it, and a template
|
|
291
|
+
// can only reach public members. `tsc` does not typecheck Angular templates, so `private` here
|
|
292
|
+
// compiles clean and fails only under `ngc --compilationMode partial`.
|
|
280
293
|
emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
|
|
281
294
|
if (isSymbioteEvent(event)) emitter.emit(event);
|
|
282
295
|
}
|
|
283
296
|
|
|
297
|
+
// The four accessibility events are boolean-GATED Fabric events
|
|
298
|
+
// (`.claude/rules/fabric-boolean-event-gates.md`) — `show`/`dismiss`/`requestClose`/
|
|
299
|
+
// `orientationChange` are NOT (Modal's own MODAL_EVENTS), so those stay unconditional template
|
|
300
|
+
// bindings above. `.observed`-gated, mirroring Pressable's.
|
|
301
|
+
private eventEmitterHandler(
|
|
302
|
+
emitter: EventEmitter<ISymbioteEvent>,
|
|
303
|
+
): ((event: unknown) => void) | undefined {
|
|
304
|
+
return emitter.observed ? event => this.emit(emitter, event) : undefined;
|
|
305
|
+
}
|
|
306
|
+
|
|
284
307
|
// Typed as the a11y intersection WITH the string index (the bag renderModal spreads into the
|
|
285
308
|
// host props), so resolveAccessibilityProps's result stays assignable to passthrough.
|
|
286
|
-
private accessibilityInputs(): IAccessibilityProps &
|
|
309
|
+
private accessibilityInputs(): IAccessibilityProps &
|
|
310
|
+
IAriaProps &
|
|
311
|
+
Record<string, unknown> {
|
|
287
312
|
return {
|
|
288
313
|
testID: this.testID,
|
|
289
314
|
accessible: this.accessible,
|
|
@@ -301,8 +326,10 @@ export class Modal implements IAngularModalInputs, OnInit, OnChanges, DoCheck {
|
|
|
301
326
|
accessibilityElementsHidden: this.accessibilityElementsHidden,
|
|
302
327
|
accessibilityIgnoresInvertColors: this.accessibilityIgnoresInvertColors,
|
|
303
328
|
accessibilityLanguage: this.accessibilityLanguage,
|
|
304
|
-
accessibilityRespondsToUserInteraction:
|
|
305
|
-
|
|
329
|
+
accessibilityRespondsToUserInteraction:
|
|
330
|
+
this.accessibilityRespondsToUserInteraction,
|
|
331
|
+
accessibilityShowsLargeContentViewer:
|
|
332
|
+
this.accessibilityShowsLargeContentViewer,
|
|
306
333
|
accessibilityLargeContentTitle: this.accessibilityLargeContentTitle,
|
|
307
334
|
role: this.role,
|
|
308
335
|
'aria-label': this.ariaLabel,
|
|
@@ -13,11 +13,14 @@ import {
|
|
|
13
13
|
ViewChild,
|
|
14
14
|
type DoCheck,
|
|
15
15
|
type OnChanges,
|
|
16
|
+
type OnDestroy,
|
|
16
17
|
} from '@angular/core';
|
|
17
18
|
import {
|
|
18
19
|
createPressHandlers,
|
|
19
20
|
createPressRuntime,
|
|
21
|
+
disposePressRuntime,
|
|
20
22
|
DEFAULT_DELAY_LONG_PRESS_MS,
|
|
23
|
+
DEFAULT_MIN_PRESS_DURATION_MS,
|
|
21
24
|
isTerminationAllowed,
|
|
22
25
|
resolveAccessibilityProps,
|
|
23
26
|
resolveDisabledAccessibilityState,
|
|
@@ -50,14 +53,23 @@ import {
|
|
|
50
53
|
SymbioteStyleInputDirective,
|
|
51
54
|
ViewHost,
|
|
52
55
|
} from '../../primitives';
|
|
56
|
+
import {
|
|
57
|
+
gateWanted,
|
|
58
|
+
injectGateDemand,
|
|
59
|
+
type IGatedAccessibilityEvent,
|
|
60
|
+
} from '../../gate-demand';
|
|
53
61
|
|
|
54
|
-
export type {
|
|
62
|
+
export type {
|
|
63
|
+
IPressState,
|
|
64
|
+
IPressableAndroidRippleConfig,
|
|
65
|
+
} from '@symbiote-native/components';
|
|
55
66
|
|
|
56
67
|
// The full logical Pressable surface, callback-shaped press/hover events included. Touchable* and
|
|
57
68
|
// TouchableNativeFeedback still take their OWN onPress/onPressIn/... as @Input() callbacks (that is
|
|
58
69
|
// a separate, not-yet-converted decision) and derive their public prop types from this - so it keeps
|
|
59
70
|
// the callback fields even though the Pressable component below no longer implements them directly.
|
|
60
|
-
export interface IAngularPressableProps
|
|
71
|
+
export interface IAngularPressableProps
|
|
72
|
+
extends IAccessibilityProps, IAriaProps {
|
|
61
73
|
onPress?: IPressHandler;
|
|
62
74
|
onPressIn?: IPressHandler;
|
|
63
75
|
onPressOut?: IPressHandler;
|
|
@@ -83,7 +95,8 @@ export interface IAngularPressableProps extends IAccessibilityProps, IAriaProps
|
|
|
83
95
|
nextFocusLeft?: number;
|
|
84
96
|
nextFocusRight?: number;
|
|
85
97
|
nextFocusUp?: number;
|
|
86
|
-
style?:
|
|
98
|
+
style?:
|
|
99
|
+
IStyleProp<IViewStyle> | ((state: IPressState) => IStyleProp<IViewStyle>);
|
|
87
100
|
}
|
|
88
101
|
|
|
89
102
|
// What the Pressable component itself takes as plain @Input()s: the full surface minus the press/
|
|
@@ -105,7 +118,9 @@ export type IAngularPressableInputs = Omit<
|
|
|
105
118
|
| 'onAccessibilityEscape'
|
|
106
119
|
>;
|
|
107
120
|
|
|
108
|
-
function isStyleFn(
|
|
121
|
+
function isStyleFn(
|
|
122
|
+
value: unknown,
|
|
123
|
+
): value is (state: IPressState) => IStyleProp<IViewStyle> {
|
|
109
124
|
return typeof value === 'function';
|
|
110
125
|
}
|
|
111
126
|
|
|
@@ -116,19 +131,28 @@ function asSymbioteEvent(event: unknown): ISymbioteEvent | undefined {
|
|
|
116
131
|
}
|
|
117
132
|
|
|
118
133
|
@Component({
|
|
119
|
-
selector
|
|
134
|
+
// Single-name selector, matching TextInput's own ('TextInput', text-input.ts) rather than
|
|
135
|
+
// View/Text's dual one. The alternate `symbiote-pressable` spelling was vestigial — nothing in
|
|
136
|
+
// this component's own template ever renders that tag (it renders `symbiote-view`, below), and
|
|
137
|
+
// no app or example code invokes the composed component by that literal tag (verified by grep,
|
|
138
|
+
// 2026-08-31). Its only effect was to force `symbiote-pressable` into
|
|
139
|
+
// `ANCHOR_HOST_COMPONENTS` unconditionally, which collided with the tag's OTHER, load-bearing
|
|
140
|
+
// meaning — the host-behavior registry key (`PRESSABLE_TAG`,
|
|
141
|
+
// core/components/src/behaviors/pressable.ts) a lowered `<Pressable>` must carry to get the
|
|
142
|
+
// press machine. That collision is exactly why `DIAGNOSTIC_LOWERED_PRESSABLE_TAG` existed
|
|
143
|
+
// (renderer/index.ts) — dropping the vestigial spelling here removes the collision at its
|
|
144
|
+
// source, so the escape hatch is retired rather than worked around.
|
|
145
|
+
selector: 'Pressable',
|
|
120
146
|
standalone: true,
|
|
121
|
-
hostDirectives: [
|
|
147
|
+
hostDirectives: [
|
|
148
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
149
|
+
],
|
|
122
150
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
123
151
|
imports: [ViewHost, SymbioteHostPropsDirective],
|
|
124
152
|
template: `
|
|
125
153
|
<symbiote-view
|
|
126
154
|
#host
|
|
127
155
|
[symbioteHostProps]="hostProps()"
|
|
128
|
-
(accessibilityAction)="emit(accessibilityAction, $event)"
|
|
129
|
-
(accessibilityTap)="emit(accessibilityTap, $event)"
|
|
130
|
-
(magicTap)="emit(magicTap, $event)"
|
|
131
|
-
(accessibilityEscape)="emit(accessibilityEscape, $event)"
|
|
132
156
|
(press)="handlePress($event)"
|
|
133
157
|
(pressIn)="handlePressIn($event)"
|
|
134
158
|
(pressOut)="handlePressOut($event)"
|
|
@@ -141,7 +165,9 @@ function asSymbioteEvent(event: unknown): ISymbioteEvent | undefined {
|
|
|
141
165
|
`,
|
|
142
166
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
143
167
|
})
|
|
144
|
-
export class Pressable
|
|
168
|
+
export class Pressable
|
|
169
|
+
implements IAngularPressableInputs, OnChanges, OnDestroy, DoCheck
|
|
170
|
+
{
|
|
145
171
|
// The press/hover lifecycle as real Angular events: `(press)="onTap($event)"`, not
|
|
146
172
|
// `[onPress]="onTap"`. createPressHandlers still wants plain IPressHandler callbacks, so
|
|
147
173
|
// emitterHandler() below adapts each EventEmitter into one - only while something is actually
|
|
@@ -154,9 +180,15 @@ export class Pressable implements IAngularPressableInputs, OnChanges, DoCheck {
|
|
|
154
180
|
@Output() readonly longPress = new EventEmitter<ISymbioteEvent>();
|
|
155
181
|
@Output() readonly hoverIn = new EventEmitter<ISymbioteEvent>();
|
|
156
182
|
@Output() readonly hoverOut = new EventEmitter<ISymbioteEvent>();
|
|
157
|
-
// The four accessibility callbacks as real Angular events
|
|
158
|
-
//
|
|
159
|
-
// `
|
|
183
|
+
// The four accessibility callbacks as real Angular events, app-facing. UNLIKE press/hover above,
|
|
184
|
+
// these are NOT bound in the template at all — `accessibilityAction`/`accessibilityTap`/
|
|
185
|
+
// `magicTap`/`accessibilityEscape` are `onX`-gated Fabric events
|
|
186
|
+
// (`.claude/rules/fabric-boolean-event-gates.md`): the engine only emits them when the committed
|
|
187
|
+
// payload carries a FUNCTION at that key, so a `(accessibilityAction)="..."` template binding —
|
|
188
|
+
// always present once written — would light the gate on every instance regardless of whether the
|
|
189
|
+
// app ever subscribes. Routed through `hostProps()` instead (`accessibilityEmitterHandler`
|
|
190
|
+
// below), the same `.observed`-conditional shape `emitterHandler` already uses for press/hover,
|
|
191
|
+
// so the gate lights only when something is actually listening.
|
|
160
192
|
@Output() readonly accessibilityAction = new EventEmitter<ISymbioteEvent>();
|
|
161
193
|
@Output() readonly accessibilityTap = new EventEmitter<ISymbioteEvent>();
|
|
162
194
|
@Output() readonly magicTap = new EventEmitter<ISymbioteEvent>();
|
|
@@ -167,6 +199,8 @@ export class Pressable implements IAngularPressableInputs, OnChanges, DoCheck {
|
|
|
167
199
|
@Input() hitSlop?: IRectOffset;
|
|
168
200
|
@Input() pressRetentionOffset?: IRectOffset;
|
|
169
201
|
@Input() unstable_pressDelay?: number;
|
|
202
|
+
/** @internal Touchable* mirrors RN's Pressability minPressDuration: 0 override. */
|
|
203
|
+
@Input() __minPressDuration = DEFAULT_MIN_PRESS_DURATION_MS;
|
|
170
204
|
@Input() android_ripple?: IPressableAndroidRippleConfig;
|
|
171
205
|
@Input() android_disableSound?: boolean;
|
|
172
206
|
@Input() delayHoverIn?: number;
|
|
@@ -186,10 +220,13 @@ export class Pressable implements IAngularPressableInputs, OnChanges, DoCheck {
|
|
|
186
220
|
@Input() accessibilityRole?: IAngularPressableProps['accessibilityRole'];
|
|
187
221
|
@Input() accessibilityState?: IAccessibilityStateValue;
|
|
188
222
|
@Input() accessibilityValue?: IAngularPressableProps['accessibilityValue'];
|
|
189
|
-
@Input()
|
|
223
|
+
@Input()
|
|
224
|
+
accessibilityActions?: IAngularPressableProps['accessibilityActions'];
|
|
190
225
|
@Input() accessibilityLabelledBy?: string | string[];
|
|
191
|
-
@Input()
|
|
192
|
-
|
|
226
|
+
@Input()
|
|
227
|
+
importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
|
|
228
|
+
@Input()
|
|
229
|
+
accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
|
|
193
230
|
@Input() screenReaderFocusable?: boolean;
|
|
194
231
|
@Input() accessibilityViewIsModal?: boolean;
|
|
195
232
|
@Input() accessibilityElementsHidden?: boolean;
|
|
@@ -215,7 +252,8 @@ export class Pressable implements IAngularPressableInputs, OnChanges, DoCheck {
|
|
|
215
252
|
@Input() id?: string;
|
|
216
253
|
@Input() role?: IAngularPressableProps['role'];
|
|
217
254
|
|
|
218
|
-
@ViewChild('host', { read: ElementRef })
|
|
255
|
+
@ViewChild('host', { read: ElementRef })
|
|
256
|
+
private hostElement?: ElementRef<unknown>;
|
|
219
257
|
|
|
220
258
|
// INTERNAL mutable state, driven by the press machine rather than by an @Input, so ngOnChanges
|
|
221
259
|
// can never cover it - it carries its OWN signal, which is what lets the memoized bag below stay
|
|
@@ -242,9 +280,14 @@ export class Pressable implements IAngularPressableInputs, OnChanges, DoCheck {
|
|
|
242
280
|
const id = setTimeout(callback, ms);
|
|
243
281
|
return () => clearTimeout(id);
|
|
244
282
|
},
|
|
283
|
+
now: Date.now,
|
|
245
284
|
};
|
|
246
285
|
|
|
247
286
|
private readonly changeDetector = inject(ChangeDetectorRef);
|
|
287
|
+
// Null unless an adapter wrapper renders this Pressable in its own template. See
|
|
288
|
+
// `gate-demand.ts`; an app's own `<Pressable>` — including one projected INTO a wrapper — gets
|
|
289
|
+
// null and falls back to its own `.observed`.
|
|
290
|
+
private readonly gateDemand = injectGateDemand();
|
|
248
291
|
// This component's OWN host - the non-painting anchor `class="..."` at the use site resolves
|
|
249
292
|
// onto (see anchorHostStyle's doc comment) - NOT `hostElement` above, which targets the real
|
|
250
293
|
// inner `symbiote-view` one level down.
|
|
@@ -276,6 +319,10 @@ export class Pressable implements IAngularPressableInputs, OnChanges, DoCheck {
|
|
|
276
319
|
this.anchorStyle.set(anchorHostStyle(this.elementRef));
|
|
277
320
|
}
|
|
278
321
|
|
|
322
|
+
ngOnDestroy(): void {
|
|
323
|
+
disposePressRuntime(this.runtime);
|
|
324
|
+
}
|
|
325
|
+
|
|
279
326
|
ngOnChanges(): void {
|
|
280
327
|
// Tells the computed bag below that an @Input moved - plain class fields are not reactive on
|
|
281
328
|
// their own. Must stay in ngOnChanges: that is the single moment Angular has finished writing
|
|
@@ -293,7 +340,10 @@ export class Pressable implements IAngularPressableInputs, OnChanges, DoCheck {
|
|
|
293
340
|
accessibilityLabel: this.accessibilityLabel,
|
|
294
341
|
accessibilityHint: this.accessibilityHint,
|
|
295
342
|
accessibilityRole: this.accessibilityRole,
|
|
296
|
-
accessibilityState: resolveDisabledAccessibilityState(
|
|
343
|
+
accessibilityState: resolveDisabledAccessibilityState(
|
|
344
|
+
this.accessibilityState,
|
|
345
|
+
this.disabled,
|
|
346
|
+
),
|
|
297
347
|
accessibilityValue: this.accessibilityValue,
|
|
298
348
|
accessibilityActions: this.accessibilityActions,
|
|
299
349
|
accessibilityLabelledBy: this.accessibilityLabelledBy,
|
|
@@ -304,8 +354,10 @@ export class Pressable implements IAngularPressableInputs, OnChanges, DoCheck {
|
|
|
304
354
|
accessibilityElementsHidden: this.accessibilityElementsHidden,
|
|
305
355
|
accessibilityIgnoresInvertColors: this.accessibilityIgnoresInvertColors,
|
|
306
356
|
accessibilityLanguage: this.accessibilityLanguage,
|
|
307
|
-
accessibilityRespondsToUserInteraction:
|
|
308
|
-
|
|
357
|
+
accessibilityRespondsToUserInteraction:
|
|
358
|
+
this.accessibilityRespondsToUserInteraction,
|
|
359
|
+
accessibilityShowsLargeContentViewer:
|
|
360
|
+
this.accessibilityShowsLargeContentViewer,
|
|
309
361
|
accessibilityLargeContentTitle: this.accessibilityLargeContentTitle,
|
|
310
362
|
'aria-label': this.ariaLabel,
|
|
311
363
|
'aria-busy': this.ariaBusy,
|
|
@@ -327,7 +379,9 @@ export class Pressable implements IAngularPressableInputs, OnChanges, DoCheck {
|
|
|
327
379
|
}
|
|
328
380
|
|
|
329
381
|
get resolvedRippleProps(): Record<string, unknown> | undefined {
|
|
330
|
-
return this.android_ripple === undefined
|
|
382
|
+
return this.android_ripple === undefined
|
|
383
|
+
? undefined
|
|
384
|
+
: rippleProps(this.android_ripple);
|
|
331
385
|
}
|
|
332
386
|
|
|
333
387
|
// Flat bag for `[symbioteHostProps]` (Renderer2.setProperty per key) - see primitives/shared.ts.
|
|
@@ -351,33 +405,47 @@ export class Pressable implements IAngularPressableInputs, OnChanges, DoCheck {
|
|
|
351
405
|
style: [this.anchorStyle(), this.resolvedStyle],
|
|
352
406
|
accessible: this.accessible,
|
|
353
407
|
...this.foldedAccessibility,
|
|
408
|
+
onAccessibilityAction: this.accessibilityEmitterHandler(
|
|
409
|
+
'accessibilityAction',
|
|
410
|
+
),
|
|
411
|
+
onAccessibilityTap: this.accessibilityEmitterHandler('accessibilityTap'),
|
|
412
|
+
onMagicTap: this.accessibilityEmitterHandler('magicTap'),
|
|
413
|
+
onAccessibilityEscape: this.accessibilityEmitterHandler(
|
|
414
|
+
'accessibilityEscape',
|
|
415
|
+
),
|
|
354
416
|
android_disableSound: this.android_disableSound,
|
|
355
|
-
nativeBackgroundAndroid:
|
|
356
|
-
|
|
417
|
+
nativeBackgroundAndroid:
|
|
418
|
+
this.resolvedRippleProps?.nativeBackgroundAndroid,
|
|
419
|
+
nativeForegroundAndroid:
|
|
420
|
+
this.resolvedRippleProps?.nativeForegroundAndroid,
|
|
357
421
|
};
|
|
358
422
|
});
|
|
359
423
|
|
|
360
424
|
handlePressIn(event: unknown): void {
|
|
361
425
|
const symbioteEvent = asSymbioteEvent(event);
|
|
362
|
-
if (shouldSuppressPress(this.disabled) || symbioteEvent === undefined)
|
|
426
|
+
if (shouldSuppressPress(this.disabled) || symbioteEvent === undefined)
|
|
427
|
+
return;
|
|
363
428
|
this.handlers.handlePressIn(symbioteEvent);
|
|
364
429
|
}
|
|
365
430
|
|
|
366
431
|
handlePressOut(event: unknown): void {
|
|
367
432
|
const symbioteEvent = asSymbioteEvent(event);
|
|
368
|
-
if (shouldSuppressPress(this.disabled) || symbioteEvent === undefined)
|
|
433
|
+
if (shouldSuppressPress(this.disabled) || symbioteEvent === undefined)
|
|
434
|
+
return;
|
|
369
435
|
this.handlers.handlePressOut(symbioteEvent);
|
|
370
436
|
}
|
|
371
437
|
|
|
372
438
|
handlePress(event: unknown): void {
|
|
373
439
|
const symbioteEvent = asSymbioteEvent(event);
|
|
374
|
-
if (shouldSuppressPress(this.disabled) || symbioteEvent === undefined)
|
|
440
|
+
if (shouldSuppressPress(this.disabled) || symbioteEvent === undefined)
|
|
441
|
+
return;
|
|
375
442
|
this.handlers.handlePress(symbioteEvent);
|
|
376
443
|
}
|
|
377
444
|
|
|
378
445
|
handleResponderMove(event: unknown): void {
|
|
379
446
|
const symbioteEvent = asSymbioteEvent(event);
|
|
380
|
-
if (shouldSuppressPress(this.disabled) || symbioteEvent === undefined)
|
|
447
|
+
if (shouldSuppressPress(this.disabled) || symbioteEvent === undefined)
|
|
448
|
+
return;
|
|
381
449
|
this.handlers.handleResponderMove(symbioteEvent);
|
|
382
450
|
}
|
|
383
451
|
|
|
@@ -389,7 +457,7 @@ export class Pressable implements IAngularPressableInputs, OnChanges, DoCheck {
|
|
|
389
457
|
return isTerminationAllowed(this.cancelable);
|
|
390
458
|
}
|
|
391
459
|
|
|
392
|
-
emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
|
|
460
|
+
private emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
|
|
393
461
|
const symbioteEvent = asSymbioteEvent(event);
|
|
394
462
|
if (symbioteEvent !== undefined) emitter.emit(symbioteEvent);
|
|
395
463
|
}
|
|
@@ -398,17 +466,38 @@ export class Pressable implements IAngularPressableInputs, OnChanges, DoCheck {
|
|
|
398
466
|
// is listening anyway, mirroring core's noteHoverNoop but off `.observed` instead of an @Input.
|
|
399
467
|
private noteHoverNoop(): void {
|
|
400
468
|
if (this.hoverIn.observed || this.hoverOut.observed) {
|
|
401
|
-
dlog(
|
|
469
|
+
dlog(
|
|
470
|
+
'Pressable hover is a no-op on this host (no pointer-enter/leave event)',
|
|
471
|
+
);
|
|
402
472
|
}
|
|
403
473
|
}
|
|
404
474
|
|
|
405
475
|
// Wraps an @Output() as an IPressHandler only while it has a subscriber, so an unbound
|
|
406
476
|
// (longPress) still skips arming the timer, matching createPressHandlers' "undefined means
|
|
407
477
|
// nobody cares" contract.
|
|
408
|
-
private emitterHandler(
|
|
478
|
+
private emitterHandler(
|
|
479
|
+
emitter: EventEmitter<ISymbioteEvent>,
|
|
480
|
+
): IPressHandler | undefined {
|
|
409
481
|
return emitter.observed ? event => emitter.emit(event) : undefined;
|
|
410
482
|
}
|
|
411
483
|
|
|
484
|
+
// The accessibility twin of emitterHandler, feeding hostProps()'s flat bag instead of
|
|
485
|
+
// createPressHandlers' config — see the accessibilityAction @Output()'s own comment for why
|
|
486
|
+
// these four specifically cannot be plain template `(x)="..."` bindings. `undefined` here means
|
|
487
|
+
// `setEventListener` never sees a function, so the gated prop's flag never lights
|
|
488
|
+
// (`fabric-boolean-event-gates.md`, "The flag follows the VALUE, not the key").
|
|
489
|
+
private accessibilityEmitterHandler(
|
|
490
|
+
name: IGatedAccessibilityEvent,
|
|
491
|
+
): ((event: unknown) => void) | undefined {
|
|
492
|
+
const emitter = this[name];
|
|
493
|
+
// A WRAPPER's binding is not a subscriber, and `.observed` cannot tell the two apart. When a
|
|
494
|
+
// demand is supplied from above it decides instead — see `gate-demand.ts` for why the channel
|
|
495
|
+
// is DI rather than an `@Input`.
|
|
496
|
+
return gateWanted(this.gateDemand, name, emitter)
|
|
497
|
+
? event => this.emit(emitter, event)
|
|
498
|
+
: undefined;
|
|
499
|
+
}
|
|
500
|
+
|
|
412
501
|
private get handlers() {
|
|
413
502
|
this.noteHoverNoop();
|
|
414
503
|
const config: IPressMachineConfig = {
|
|
@@ -419,6 +508,7 @@ export class Pressable implements IAngularPressableInputs, OnChanges, DoCheck {
|
|
|
419
508
|
onLongPress: this.emitterHandler(this.longPress),
|
|
420
509
|
delayLongPress: this.delayLongPress ?? DEFAULT_DELAY_LONG_PRESS_MS,
|
|
421
510
|
unstable_pressDelay: this.unstable_pressDelay ?? 0,
|
|
511
|
+
minPressDuration: this.__minPressDuration,
|
|
422
512
|
hitSlop: this.hitSlop,
|
|
423
513
|
pressRetentionOffset: this.pressRetentionOffset,
|
|
424
514
|
};
|
|
@@ -60,7 +60,8 @@ type IHostProps = Record<string, unknown>;
|
|
|
60
60
|
// via <ng-content>), declared per-adapter over the shared accessibility base since the framework-specific
|
|
61
61
|
// children slot keeps it from being fully shared across adapters. Read the React reference for the
|
|
62
62
|
// native prop names.
|
|
63
|
-
export interface IAngularRefreshControlProps
|
|
63
|
+
export interface IAngularRefreshControlProps
|
|
64
|
+
extends IAccessibilityProps, IAriaProps {
|
|
64
65
|
refreshing: boolean;
|
|
65
66
|
// RN's onRefresh is `() => void | Promise<void>`, the handler may be async; the promise is
|
|
66
67
|
// fire-and-forget (native already starts refreshing on the gesture).
|
|
@@ -97,7 +98,9 @@ export type IAngularRefreshControlInputs = Omit<
|
|
|
97
98
|
@Component({
|
|
98
99
|
selector: 'RefreshControl',
|
|
99
100
|
standalone: true,
|
|
100
|
-
hostDirectives: [
|
|
101
|
+
hostDirectives: [
|
|
102
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
103
|
+
],
|
|
101
104
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
102
105
|
imports: [RefreshControlHost, SymbioteHostPropsDirective],
|
|
103
106
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -106,17 +109,18 @@ export type IAngularRefreshControlInputs = Omit<
|
|
|
106
109
|
#host
|
|
107
110
|
[symbioteHostProps]="hostProps()"
|
|
108
111
|
(refresh)="handleRefresh()"
|
|
109
|
-
(accessibilityAction)="emit(accessibilityAction, $event)"
|
|
110
|
-
(accessibilityTap)="emit(accessibilityTap, $event)"
|
|
111
|
-
(magicTap)="emit(magicTap, $event)"
|
|
112
|
-
(accessibilityEscape)="emit(accessibilityEscape, $event)"
|
|
113
112
|
>
|
|
114
113
|
<ng-content></ng-content>
|
|
115
114
|
</symbiote-refresh-control>
|
|
116
115
|
`,
|
|
117
116
|
})
|
|
118
117
|
export class RefreshControl
|
|
119
|
-
implements
|
|
118
|
+
implements
|
|
119
|
+
IAngularRefreshControlInputs,
|
|
120
|
+
OnInit,
|
|
121
|
+
OnChanges,
|
|
122
|
+
DoCheck,
|
|
123
|
+
AfterViewInit
|
|
120
124
|
{
|
|
121
125
|
// Controlled prop the parent owns; required to match the React reference surface.
|
|
122
126
|
@Input({ required: true }) refreshing!: boolean;
|
|
@@ -142,8 +146,10 @@ export class RefreshControl
|
|
|
142
146
|
@Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
|
|
143
147
|
@Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
|
|
144
148
|
@Input() accessibilityLabelledBy?: string | string[];
|
|
145
|
-
@Input()
|
|
146
|
-
|
|
149
|
+
@Input()
|
|
150
|
+
importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
|
|
151
|
+
@Input()
|
|
152
|
+
accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
|
|
147
153
|
@Input() screenReaderFocusable?: boolean;
|
|
148
154
|
@Input() accessibilityViewIsModal?: boolean;
|
|
149
155
|
@Input() accessibilityElementsHidden?: boolean;
|
|
@@ -218,7 +224,9 @@ export class RefreshControl
|
|
|
218
224
|
this.lastNativeRefreshing = this.refreshing;
|
|
219
225
|
return;
|
|
220
226
|
}
|
|
221
|
-
this.syncNativeRefreshing(
|
|
227
|
+
this.syncNativeRefreshing(
|
|
228
|
+
this.refreshNativeNode ?? this.host?.nativeElement,
|
|
229
|
+
);
|
|
222
230
|
}
|
|
223
231
|
|
|
224
232
|
// Native starts the spinner before JS runs. Mirror RN's RefreshControl controlled-component
|
|
@@ -240,10 +248,19 @@ export class RefreshControl
|
|
|
240
248
|
|
|
241
249
|
// Forward an engine event to the matching @Output(), narrowing the template's untyped $event
|
|
242
250
|
// first. The accessibility* events arrive on the engine's structural event channel.
|
|
243
|
-
emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
|
|
251
|
+
private emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
|
|
244
252
|
if (isSymbioteEvent(event)) emitter.emit(event);
|
|
245
253
|
}
|
|
246
254
|
|
|
255
|
+
// The four accessibility events are boolean-GATED Fabric events
|
|
256
|
+
// (`.claude/rules/fabric-boolean-event-gates.md`): a template binding here lit the gate on every
|
|
257
|
+
// instance whether or not an app ever subscribed. `.observed`-gated, mirroring Pressable's.
|
|
258
|
+
private eventEmitterHandler(
|
|
259
|
+
emitter: EventEmitter<ISymbioteEvent>,
|
|
260
|
+
): ((event: unknown) => void) | undefined {
|
|
261
|
+
return emitter.observed ? event => this.emit(emitter, event) : undefined;
|
|
262
|
+
}
|
|
263
|
+
|
|
247
264
|
// The anchor's class-derived style is NOT an @Input: `class="..."`/`[ngClass]` at the use site
|
|
248
265
|
// resolves through the renderer's addClass onto this component's anchor host (see
|
|
249
266
|
// anchorHostStyle's doc comment), so it never shows up in SimpleChanges and ngOnChanges alone
|
|
@@ -282,6 +299,10 @@ export class RefreshControl
|
|
|
282
299
|
nativeID: this.nativeID,
|
|
283
300
|
accessible: this.accessible,
|
|
284
301
|
...this.folded,
|
|
302
|
+
onAccessibilityAction: this.eventEmitterHandler(this.accessibilityAction),
|
|
303
|
+
onAccessibilityTap: this.eventEmitterHandler(this.accessibilityTap),
|
|
304
|
+
onMagicTap: this.eventEmitterHandler(this.magicTap),
|
|
305
|
+
onAccessibilityEscape: this.eventEmitterHandler(this.accessibilityEscape),
|
|
285
306
|
};
|
|
286
307
|
});
|
|
287
308
|
|
|
@@ -303,8 +324,10 @@ export class RefreshControl
|
|
|
303
324
|
accessibilityElementsHidden: this.accessibilityElementsHidden,
|
|
304
325
|
accessibilityIgnoresInvertColors: this.accessibilityIgnoresInvertColors,
|
|
305
326
|
accessibilityLanguage: this.accessibilityLanguage,
|
|
306
|
-
accessibilityRespondsToUserInteraction:
|
|
307
|
-
|
|
327
|
+
accessibilityRespondsToUserInteraction:
|
|
328
|
+
this.accessibilityRespondsToUserInteraction,
|
|
329
|
+
accessibilityShowsLargeContentViewer:
|
|
330
|
+
this.accessibilityShowsLargeContentViewer,
|
|
308
331
|
accessibilityLargeContentTitle: this.accessibilityLargeContentTitle,
|
|
309
332
|
role: this.role,
|
|
310
333
|
'aria-label': this.ariaLabel,
|