@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
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { EventEmitter, computed, signal } from '@angular/core';
|
|
2
|
+
import {
|
|
3
|
+
gateWanted,
|
|
4
|
+
injectGateDemand,
|
|
5
|
+
type IGatedAccessibilityEvent,
|
|
6
|
+
} from '../../gate-demand';
|
|
2
7
|
import {
|
|
3
8
|
imageStatics,
|
|
4
9
|
renderImage,
|
|
@@ -150,15 +155,29 @@ function asNumber(value: unknown): number | undefined {
|
|
|
150
155
|
return typeof value === 'number' ? value : undefined;
|
|
151
156
|
}
|
|
152
157
|
|
|
153
|
-
function asCrossOrigin(
|
|
154
|
-
|
|
158
|
+
function asCrossOrigin(
|
|
159
|
+
value: unknown,
|
|
160
|
+
): 'anonymous' | 'use-credentials' | undefined {
|
|
161
|
+
return value === 'anonymous' || value === 'use-credentials'
|
|
162
|
+
? value
|
|
163
|
+
: undefined;
|
|
155
164
|
}
|
|
156
165
|
|
|
157
166
|
function asStyle(value: unknown): IStyleProp<IViewStyle> | undefined {
|
|
158
167
|
return typeof value === 'object' && value !== null ? value : undefined;
|
|
159
168
|
}
|
|
160
169
|
|
|
161
|
-
|
|
170
|
+
// Narrows a value out of the untyped animated prop bag. A runtime guard rather than a cast: the
|
|
171
|
+
// bag is built at runtime from merged animated values, so nothing upstream has proven the shape.
|
|
172
|
+
export function isImageEventCallback(
|
|
173
|
+
value: unknown,
|
|
174
|
+
): value is (event: ISymbioteEvent) => void {
|
|
175
|
+
return typeof value === 'function';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function resolveImageProps(
|
|
179
|
+
input: Record<string, unknown>,
|
|
180
|
+
): Record<string, unknown> {
|
|
162
181
|
const passthrough: IImagePassthroughProps = {
|
|
163
182
|
resizeMethod: input['resizeMethod'] as IImageProps['resizeMethod'],
|
|
164
183
|
blurRadius: asNumber(input['blurRadius']),
|
|
@@ -170,12 +189,21 @@ export function resolveImageProps(input: Record<string, unknown>): Record<string
|
|
|
170
189
|
: undefined,
|
|
171
190
|
testID: asString(input['testID']),
|
|
172
191
|
nativeID: asString(input['nativeID']),
|
|
173
|
-
accessible:
|
|
192
|
+
accessible:
|
|
193
|
+
typeof input['accessible'] === 'boolean'
|
|
194
|
+
? input['accessible']
|
|
195
|
+
: undefined,
|
|
174
196
|
accessibilityLabel: asString(input['accessibilityLabel']),
|
|
175
197
|
accessibilityHint: asString(input['accessibilityHint']),
|
|
176
|
-
accessibilityRole: input[
|
|
177
|
-
|
|
178
|
-
|
|
198
|
+
accessibilityRole: input[
|
|
199
|
+
'accessibilityRole'
|
|
200
|
+
] as IAccessibilityProps['accessibilityRole'],
|
|
201
|
+
accessibilityState: input[
|
|
202
|
+
'accessibilityState'
|
|
203
|
+
] as IAccessibilityProps['accessibilityState'],
|
|
204
|
+
accessibilityValue: input[
|
|
205
|
+
'accessibilityValue'
|
|
206
|
+
] as IAccessibilityProps['accessibilityValue'],
|
|
179
207
|
accessibilityActions: input[
|
|
180
208
|
'accessibilityActions'
|
|
181
209
|
] as IAccessibilityProps['accessibilityActions'],
|
|
@@ -213,26 +241,48 @@ export function resolveImageProps(input: Record<string, unknown>): Record<string
|
|
|
213
241
|
typeof input['accessibilityShowsLargeContentViewer'] === 'boolean'
|
|
214
242
|
? input['accessibilityShowsLargeContentViewer']
|
|
215
243
|
: undefined,
|
|
216
|
-
accessibilityLargeContentTitle: asString(
|
|
244
|
+
accessibilityLargeContentTitle: asString(
|
|
245
|
+
input['accessibilityLargeContentTitle'],
|
|
246
|
+
),
|
|
217
247
|
role: input['role'] as IAriaProps['role'],
|
|
218
248
|
'aria-label': asString(input['ariaLabel'] ?? input['aria-label']),
|
|
219
|
-
'aria-labelledby': asString(
|
|
249
|
+
'aria-labelledby': asString(
|
|
250
|
+
input['ariaLabelledBy'] ?? input['aria-labelledby'],
|
|
251
|
+
),
|
|
220
252
|
'aria-live': input['ariaLive'] as IAriaProps['aria-live'],
|
|
221
|
-
'aria-hidden':
|
|
222
|
-
|
|
253
|
+
'aria-hidden':
|
|
254
|
+
typeof input['ariaHidden'] === 'boolean'
|
|
255
|
+
? input['ariaHidden']
|
|
256
|
+
: undefined,
|
|
257
|
+
'aria-busy':
|
|
258
|
+
typeof input['ariaBusy'] === 'boolean' ? input['ariaBusy'] : undefined,
|
|
223
259
|
'aria-checked': input['ariaChecked'] as IAriaProps['aria-checked'],
|
|
224
|
-
'aria-disabled':
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
260
|
+
'aria-disabled':
|
|
261
|
+
typeof input['ariaDisabled'] === 'boolean'
|
|
262
|
+
? input['ariaDisabled']
|
|
263
|
+
: undefined,
|
|
264
|
+
'aria-expanded':
|
|
265
|
+
typeof input['ariaExpanded'] === 'boolean'
|
|
266
|
+
? input['ariaExpanded']
|
|
267
|
+
: undefined,
|
|
268
|
+
'aria-selected':
|
|
269
|
+
typeof input['ariaSelected'] === 'boolean'
|
|
270
|
+
? input['ariaSelected']
|
|
271
|
+
: undefined,
|
|
272
|
+
'aria-modal':
|
|
273
|
+
typeof input['ariaModal'] === 'boolean' ? input['ariaModal'] : undefined,
|
|
228
274
|
'aria-valuemax': asNumber(input['ariaValueMax']),
|
|
229
275
|
'aria-valuemin': asNumber(input['ariaValueMin']),
|
|
230
276
|
'aria-valuenow': asNumber(input['ariaValueNow']),
|
|
231
277
|
'aria-valuetext': asString(input['ariaValueText']),
|
|
232
|
-
onAccessibilityAction: input[
|
|
278
|
+
onAccessibilityAction: input[
|
|
279
|
+
'onAccessibilityAction'
|
|
280
|
+
] as IImageProps['onLoad'],
|
|
233
281
|
onAccessibilityTap: input['onAccessibilityTap'] as IImageProps['onLoad'],
|
|
234
282
|
onMagicTap: input['onMagicTap'] as IImageProps['onLoad'],
|
|
235
|
-
onAccessibilityEscape: input[
|
|
283
|
+
onAccessibilityEscape: input[
|
|
284
|
+
'onAccessibilityEscape'
|
|
285
|
+
] as IImageProps['onLoad'],
|
|
236
286
|
onLoadStart: input['onLoadStart'] as IImageProps['onLoadStart'],
|
|
237
287
|
onLoad: input['onLoad'] as IImageProps['onLoad'],
|
|
238
288
|
onLoadEnd: input['onLoadEnd'] as IImageProps['onLoadEnd'],
|
|
@@ -345,28 +395,32 @@ export abstract class ImageBase {
|
|
|
345
395
|
readonly progress = new EventEmitter<ISymbioteEvent>();
|
|
346
396
|
readonly partialLoad = new EventEmitter<ISymbioteEvent>();
|
|
347
397
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
this
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (!
|
|
368
|
-
|
|
369
|
-
|
|
398
|
+
// Null unless an adapter wrapper renders this Image in its own template — ImageBackground does.
|
|
399
|
+
// An app's own `<Image>` gets null and answers from its own `.observed`.
|
|
400
|
+
private readonly gateDemand = injectGateDemand();
|
|
401
|
+
|
|
402
|
+
// The four accessibility events are boolean-GATED Fabric events
|
|
403
|
+
// (`.claude/rules/fabric-boolean-event-gates.md`): native fires them only when the committed
|
|
404
|
+
// payload carries a FUNCTION at that key. A template `(accessibilityAction)="..."` binding on the
|
|
405
|
+
// intrinsic host lights the gate unconditionally; this reaches the app two ways — a plain
|
|
406
|
+
// `[onAccessibilityAction]` @Input callback, or the `accessibilityAction` @Output — and the gate
|
|
407
|
+
// must light only when at least one of the two is actually wired. Returned `undefined` means
|
|
408
|
+
// `setEventListener` never sees a function, so the gate stays dark.
|
|
409
|
+
protected gatedAccessibilityHandler(
|
|
410
|
+
name: IGatedAccessibilityEvent,
|
|
411
|
+
callback: ((event: ISymbioteEvent) => void) | undefined,
|
|
412
|
+
): ((event: Event) => void) | undefined {
|
|
413
|
+
const emitter = this[name];
|
|
414
|
+
// A WRAPPER's binding is not a subscriber, and `.observed` cannot tell the two apart.
|
|
415
|
+
// ImageBackground renders this component and binds all four, so without the demand every
|
|
416
|
+
// Image inside one lit its gates. See `gate-demand.ts`.
|
|
417
|
+
if (!gateWanted(this.gateDemand, name, emitter) && callback === undefined)
|
|
418
|
+
return undefined;
|
|
419
|
+
return event => {
|
|
420
|
+
if (!isSymbioteEvent(event)) return;
|
|
421
|
+
callback?.(event);
|
|
422
|
+
emitter.emit(event);
|
|
423
|
+
};
|
|
370
424
|
}
|
|
371
425
|
|
|
372
426
|
handleLoadStart(event: Event): void {
|
|
@@ -442,8 +496,10 @@ export abstract class ImageBase {
|
|
|
442
496
|
accessibilityElementsHidden: this.accessibilityElementsHidden,
|
|
443
497
|
accessibilityIgnoresInvertColors: this.accessibilityIgnoresInvertColors,
|
|
444
498
|
accessibilityLanguage: this.accessibilityLanguage,
|
|
445
|
-
accessibilityRespondsToUserInteraction:
|
|
446
|
-
|
|
499
|
+
accessibilityRespondsToUserInteraction:
|
|
500
|
+
this.accessibilityRespondsToUserInteraction,
|
|
501
|
+
accessibilityShowsLargeContentViewer:
|
|
502
|
+
this.accessibilityShowsLargeContentViewer,
|
|
447
503
|
accessibilityLargeContentTitle: this.accessibilityLargeContentTitle,
|
|
448
504
|
role: this.role,
|
|
449
505
|
ariaLabel: this.ariaLabel,
|
|
@@ -460,10 +516,19 @@ export abstract class ImageBase {
|
|
|
460
516
|
ariaValueMin: this.ariaValueMin,
|
|
461
517
|
ariaValueNow: this.ariaValueNow,
|
|
462
518
|
ariaValueText: this.ariaValueText,
|
|
463
|
-
onAccessibilityAction: this.
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
519
|
+
onAccessibilityAction: this.gatedAccessibilityHandler(
|
|
520
|
+
'accessibilityAction',
|
|
521
|
+
this.onAccessibilityAction,
|
|
522
|
+
),
|
|
523
|
+
onAccessibilityTap: this.gatedAccessibilityHandler(
|
|
524
|
+
'accessibilityTap',
|
|
525
|
+
this.onAccessibilityTap,
|
|
526
|
+
),
|
|
527
|
+
onMagicTap: this.gatedAccessibilityHandler('magicTap', this.onMagicTap),
|
|
528
|
+
onAccessibilityEscape: this.gatedAccessibilityHandler(
|
|
529
|
+
'accessibilityEscape',
|
|
530
|
+
this.onAccessibilityEscape,
|
|
531
|
+
),
|
|
467
532
|
onLoadStart: this.onLoadStart,
|
|
468
533
|
onLoad: this.onLoad,
|
|
469
534
|
onLoadEnd: this.onLoadEnd,
|
|
@@ -37,12 +37,26 @@ import {
|
|
|
37
37
|
type IViewStyle,
|
|
38
38
|
} from '@symbiote-native/engine';
|
|
39
39
|
import { Image } from './image';
|
|
40
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
anchorHostStyle,
|
|
42
|
+
SymbioteStyleInputDirective,
|
|
43
|
+
ViewHost,
|
|
44
|
+
} from '../primitives';
|
|
45
|
+
import {
|
|
46
|
+
gateWanted,
|
|
47
|
+
injectGateDemandAbove,
|
|
48
|
+
provideGateDemand,
|
|
49
|
+
type IGateDemand,
|
|
50
|
+
type IGatedAccessibilityEvent,
|
|
51
|
+
} from '../gate-demand';
|
|
41
52
|
|
|
42
53
|
// Mirrors React's IImageBackgroundProps minus children (Angular takes children via <ng-content>):
|
|
43
54
|
// every forwarding Image prop flows onto the inner image; `style` is the WRAPPER View style and
|
|
44
55
|
// `imageStyle` the inner image's.
|
|
45
|
-
export interface IAngularImageBackgroundProps extends Omit<
|
|
56
|
+
export interface IAngularImageBackgroundProps extends Omit<
|
|
57
|
+
IImageProps,
|
|
58
|
+
'style'
|
|
59
|
+
> {
|
|
46
60
|
style?: IStyleProp<IViewStyle>;
|
|
47
61
|
// A bare string resolves through the shared style registry, like `class` on the wrapper.
|
|
48
62
|
imageStyle?: IStyleProp<IViewStyle> | string;
|
|
@@ -68,7 +82,10 @@ export type IAngularImageBackgroundInputs = Omit<
|
|
|
68
82
|
@Component({
|
|
69
83
|
selector: 'ImageBackground',
|
|
70
84
|
standalone: true,
|
|
71
|
-
|
|
85
|
+
viewProviders: [provideGateDemand(() => ImageBackground)],
|
|
86
|
+
hostDirectives: [
|
|
87
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
88
|
+
],
|
|
72
89
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
73
90
|
imports: [Image, ViewHost],
|
|
74
91
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -108,10 +125,16 @@ export type IAngularImageBackgroundInputs = Omit<
|
|
|
108
125
|
[screenReaderFocusable]="folded.screenReaderFocusable"
|
|
109
126
|
[accessibilityViewIsModal]="folded.accessibilityViewIsModal"
|
|
110
127
|
[accessibilityElementsHidden]="folded.accessibilityElementsHidden"
|
|
111
|
-
[accessibilityIgnoresInvertColors]="
|
|
128
|
+
[accessibilityIgnoresInvertColors]="
|
|
129
|
+
folded.accessibilityIgnoresInvertColors
|
|
130
|
+
"
|
|
112
131
|
[accessibilityLanguage]="folded.accessibilityLanguage"
|
|
113
|
-
[accessibilityRespondsToUserInteraction]="
|
|
114
|
-
|
|
132
|
+
[accessibilityRespondsToUserInteraction]="
|
|
133
|
+
folded.accessibilityRespondsToUserInteraction
|
|
134
|
+
"
|
|
135
|
+
[accessibilityShowsLargeContentViewer]="
|
|
136
|
+
folded.accessibilityShowsLargeContentViewer
|
|
137
|
+
"
|
|
115
138
|
[accessibilityLargeContentTitle]="folded.accessibilityLargeContentTitle"
|
|
116
139
|
(loadStart)="loadStart.emit($event)"
|
|
117
140
|
(load)="load.emit($event)"
|
|
@@ -135,6 +158,16 @@ export class ImageBackground implements IAngularImageBackgroundInputs {
|
|
|
135
158
|
@Output() readonly accessibilityTap = new EventEmitter<ISymbioteEvent>();
|
|
136
159
|
@Output() readonly magicTap = new EventEmitter<ISymbioteEvent>();
|
|
137
160
|
@Output() readonly accessibilityEscape = new EventEmitter<ISymbioteEvent>();
|
|
161
|
+
|
|
162
|
+
// This wrapper binds the four gated accessibility events on the component it renders, which
|
|
163
|
+
// Angular forces to be unconditional and which would light that component's gates on every
|
|
164
|
+
// instance. It answers for them instead — see `gate-demand.ts`.
|
|
165
|
+
private readonly gateDemandAbove = injectGateDemandAbove();
|
|
166
|
+
|
|
167
|
+
wantsGate(name: IGatedAccessibilityEvent): boolean {
|
|
168
|
+
return gateWanted(this.gateDemandAbove, name, this[name]);
|
|
169
|
+
}
|
|
170
|
+
|
|
138
171
|
@Output() readonly loadStart = new EventEmitter<ISymbioteEvent>();
|
|
139
172
|
@Output() readonly load = new EventEmitter<ISymbioteEvent>();
|
|
140
173
|
@Output() readonly loadEnd = new EventEmitter<ISymbioteEvent>();
|
|
@@ -170,8 +203,10 @@ export class ImageBackground implements IAngularImageBackgroundInputs {
|
|
|
170
203
|
@Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
|
|
171
204
|
@Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
|
|
172
205
|
@Input() accessibilityLabelledBy?: string | string[];
|
|
173
|
-
@Input()
|
|
174
|
-
|
|
206
|
+
@Input()
|
|
207
|
+
importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
|
|
208
|
+
@Input()
|
|
209
|
+
accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
|
|
175
210
|
@Input() screenReaderFocusable?: boolean;
|
|
176
211
|
@Input() accessibilityViewIsModal?: boolean;
|
|
177
212
|
@Input() accessibilityElementsHidden?: boolean;
|
|
@@ -248,8 +283,10 @@ export class ImageBackground implements IAngularImageBackgroundInputs {
|
|
|
248
283
|
accessibilityElementsHidden: this.accessibilityElementsHidden,
|
|
249
284
|
accessibilityIgnoresInvertColors: this.accessibilityIgnoresInvertColors,
|
|
250
285
|
accessibilityLanguage: this.accessibilityLanguage,
|
|
251
|
-
accessibilityRespondsToUserInteraction:
|
|
252
|
-
|
|
286
|
+
accessibilityRespondsToUserInteraction:
|
|
287
|
+
this.accessibilityRespondsToUserInteraction,
|
|
288
|
+
accessibilityShowsLargeContentViewer:
|
|
289
|
+
this.accessibilityShowsLargeContentViewer,
|
|
253
290
|
accessibilityLargeContentTitle: this.accessibilityLargeContentTitle,
|
|
254
291
|
role: this.role,
|
|
255
292
|
'aria-label': this.ariaLabel,
|
|
@@ -43,7 +43,8 @@ import {
|
|
|
43
43
|
|
|
44
44
|
// Mirrors React's IInputAccessoryViewProps minus children (Angular takes children via
|
|
45
45
|
// <ng-content>), declared per-adapter over the shared a11y base.
|
|
46
|
-
export interface IAngularInputAccessoryViewProps
|
|
46
|
+
export interface IAngularInputAccessoryViewProps
|
|
47
|
+
extends IAccessibilityProps, IAriaProps {
|
|
47
48
|
// The id a TextInput's inputAccessoryViewID points at to dock above its keyboard.
|
|
48
49
|
nativeID?: string;
|
|
49
50
|
backgroundColor?: string;
|
|
@@ -55,29 +56,30 @@ export interface IAngularInputAccessoryViewProps extends IAccessibilityProps, IA
|
|
|
55
56
|
// mirroring Pressable's IAngularPressableInputs split.
|
|
56
57
|
export type IAngularInputAccessoryViewInputs = Omit<
|
|
57
58
|
IAngularInputAccessoryViewProps,
|
|
58
|
-
|
|
59
|
+
| 'onAccessibilityAction'
|
|
60
|
+
| 'onAccessibilityTap'
|
|
61
|
+
| 'onMagicTap'
|
|
62
|
+
| 'onAccessibilityEscape'
|
|
59
63
|
>;
|
|
60
64
|
|
|
61
65
|
@Component({
|
|
62
66
|
selector: 'InputAccessoryView',
|
|
63
67
|
standalone: true,
|
|
64
|
-
hostDirectives: [
|
|
68
|
+
hostDirectives: [
|
|
69
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
70
|
+
],
|
|
65
71
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
66
72
|
imports: [InputAccessoryViewHost, SymbioteHostPropsDirective],
|
|
67
73
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
68
74
|
template: `
|
|
69
|
-
<symbiote-input-accessory-view
|
|
70
|
-
[symbioteHostProps]="hostProps()"
|
|
71
|
-
(accessibilityAction)="emit(accessibilityAction, $event)"
|
|
72
|
-
(accessibilityTap)="emit(accessibilityTap, $event)"
|
|
73
|
-
(magicTap)="emit(magicTap, $event)"
|
|
74
|
-
(accessibilityEscape)="emit(accessibilityEscape, $event)"
|
|
75
|
-
>
|
|
75
|
+
<symbiote-input-accessory-view [symbioteHostProps]="hostProps()">
|
|
76
76
|
<ng-content></ng-content>
|
|
77
77
|
</symbiote-input-accessory-view>
|
|
78
78
|
`,
|
|
79
79
|
})
|
|
80
|
-
export class InputAccessoryView
|
|
80
|
+
export class InputAccessoryView
|
|
81
|
+
implements IAngularInputAccessoryViewInputs, OnChanges, DoCheck
|
|
82
|
+
{
|
|
81
83
|
@Input() nativeID?: string;
|
|
82
84
|
@Input() backgroundColor?: string;
|
|
83
85
|
@Input() style?: IStyleProp<IViewStyle>;
|
|
@@ -94,8 +96,10 @@ export class InputAccessoryView implements IAngularInputAccessoryViewInputs, OnC
|
|
|
94
96
|
@Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
|
|
95
97
|
@Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
|
|
96
98
|
@Input() accessibilityLabelledBy?: string | string[];
|
|
97
|
-
@Input()
|
|
98
|
-
|
|
99
|
+
@Input()
|
|
100
|
+
importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
|
|
101
|
+
@Input()
|
|
102
|
+
accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
|
|
99
103
|
@Input() screenReaderFocusable?: boolean;
|
|
100
104
|
@Input() accessibilityViewIsModal?: boolean;
|
|
101
105
|
@Input() accessibilityElementsHidden?: boolean;
|
|
@@ -126,10 +130,18 @@ export class InputAccessoryView implements IAngularInputAccessoryViewInputs, OnC
|
|
|
126
130
|
private readonly elementRef = inject(ElementRef);
|
|
127
131
|
|
|
128
132
|
// Forward an engine event to the matching @Output(), narrowing the template's untyped $event.
|
|
129
|
-
emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
|
|
133
|
+
private emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
|
|
130
134
|
if (isSymbioteEvent(event)) emitter.emit(event);
|
|
131
135
|
}
|
|
132
136
|
|
|
137
|
+
// The four accessibility events are boolean-GATED Fabric events
|
|
138
|
+
// (`.claude/rules/fabric-boolean-event-gates.md`). `.observed`-gated, mirroring Pressable's.
|
|
139
|
+
private eventEmitterHandler(
|
|
140
|
+
emitter: EventEmitter<ISymbioteEvent>,
|
|
141
|
+
): ((event: unknown) => void) | undefined {
|
|
142
|
+
return emitter.observed ? event => this.emit(emitter, event) : undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
133
145
|
// renderInputAccessoryView owns the host-node assembly (shared with React/Vue); the adapter reads
|
|
134
146
|
// the resolved host props off the Descriptor it returns.
|
|
135
147
|
private get descriptor() {
|
|
@@ -177,12 +189,21 @@ export class InputAccessoryView implements IAngularInputAccessoryViewInputs, OnC
|
|
|
177
189
|
readonly hostProps = computed<Record<string, unknown>>(() => {
|
|
178
190
|
this.hostPropsRevision();
|
|
179
191
|
const descriptorProps = this.descriptor.props;
|
|
180
|
-
return {
|
|
192
|
+
return {
|
|
193
|
+
...descriptorProps,
|
|
194
|
+
style: [anchorHostStyle(this.elementRef), descriptorProps.style],
|
|
195
|
+
onAccessibilityAction: this.eventEmitterHandler(this.accessibilityAction),
|
|
196
|
+
onAccessibilityTap: this.eventEmitterHandler(this.accessibilityTap),
|
|
197
|
+
onMagicTap: this.eventEmitterHandler(this.magicTap),
|
|
198
|
+
onAccessibilityEscape: this.eventEmitterHandler(this.accessibilityEscape),
|
|
199
|
+
};
|
|
181
200
|
});
|
|
182
201
|
|
|
183
202
|
// Typed as the a11y intersection WITH the string index (the bag renderInputAccessoryView spreads
|
|
184
203
|
// into the host props), so resolveAccessibilityProps's result stays assignable to passthrough.
|
|
185
|
-
private accessibilityInputs(): IAccessibilityProps &
|
|
204
|
+
private accessibilityInputs(): IAccessibilityProps &
|
|
205
|
+
IAriaProps &
|
|
206
|
+
Record<string, unknown> {
|
|
186
207
|
return {
|
|
187
208
|
testID: this.testID,
|
|
188
209
|
accessible: this.accessible,
|
|
@@ -200,8 +221,10 @@ export class InputAccessoryView implements IAngularInputAccessoryViewInputs, OnC
|
|
|
200
221
|
accessibilityElementsHidden: this.accessibilityElementsHidden,
|
|
201
222
|
accessibilityIgnoresInvertColors: this.accessibilityIgnoresInvertColors,
|
|
202
223
|
accessibilityLanguage: this.accessibilityLanguage,
|
|
203
|
-
accessibilityRespondsToUserInteraction:
|
|
204
|
-
|
|
224
|
+
accessibilityRespondsToUserInteraction:
|
|
225
|
+
this.accessibilityRespondsToUserInteraction,
|
|
226
|
+
accessibilityShowsLargeContentViewer:
|
|
227
|
+
this.accessibilityShowsLargeContentViewer,
|
|
205
228
|
accessibilityLargeContentTitle: this.accessibilityLargeContentTitle,
|
|
206
229
|
role: this.role,
|
|
207
230
|
'aria-label': this.ariaLabel,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// keyboard's way as it shows/hides. The inset math + the behavior → style/structure decision
|
|
3
3
|
// live framework-agnostic in @symbiote-native/components (render-keyboard-avoiding-view), shared verbatim
|
|
4
4
|
// with React/Vue; Angular supplies only the lifecycle: a plain inset field, ngOnInit subscribes to
|
|
5
|
-
// the core Keyboard module (
|
|
5
|
+
// the core Keyboard module (the host's show / hide pair) and markForCheck pulls the OnPush view (the
|
|
6
6
|
// Angular twin of React's setState / Vue's reactive ref), ngOnDestroy tears the subscriptions down,
|
|
7
7
|
// and the wrapper's onLayout measures the frame that feeds the next event's inset. The user
|
|
8
8
|
// children nest under the wrapper (or, for 'position', an inner View) via <ng-content>. No native
|
|
@@ -26,8 +26,10 @@ import {
|
|
|
26
26
|
} from '@angular/core';
|
|
27
27
|
import {
|
|
28
28
|
computeInset,
|
|
29
|
+
keyboardAvoidingEventNamesFor,
|
|
29
30
|
readKeyboardFrame,
|
|
30
31
|
readLayoutFrame,
|
|
32
|
+
readPrefersCrossFadeTransitions,
|
|
31
33
|
resolveAccessibilityProps,
|
|
32
34
|
resolveKeyboardAvoidingLayout,
|
|
33
35
|
DEFAULT_VERTICAL_OFFSET,
|
|
@@ -40,7 +42,7 @@ import {
|
|
|
40
42
|
} from '@symbiote-native/components';
|
|
41
43
|
import {
|
|
42
44
|
Keyboard,
|
|
43
|
-
|
|
45
|
+
Platform,
|
|
44
46
|
dlog,
|
|
45
47
|
isSymbioteEvent,
|
|
46
48
|
type IEventSubscription,
|
|
@@ -60,7 +62,8 @@ export type { IKeyboardAvoidingBehavior } from '@symbiote-native/components';
|
|
|
60
62
|
// Mirrors React's IKeyboardAvoidingViewProps minus children (Angular takes children via
|
|
61
63
|
// <ng-content>), declared per-adapter over the shared accessibility base since a framework-specific
|
|
62
64
|
// children field keeps it from being fully shared across adapters.
|
|
63
|
-
export interface IAngularKeyboardAvoidingViewProps
|
|
65
|
+
export interface IAngularKeyboardAvoidingViewProps
|
|
66
|
+
extends IAccessibilityProps, IAriaProps {
|
|
64
67
|
behavior?: IKeyboardAvoidingBehavior;
|
|
65
68
|
enabled?: boolean;
|
|
66
69
|
keyboardVerticalOffset?: number;
|
|
@@ -84,20 +87,24 @@ export type IAngularKeyboardAvoidingViewInputs = Omit<
|
|
|
84
87
|
@Component({
|
|
85
88
|
selector: 'KeyboardAvoidingView',
|
|
86
89
|
standalone: true,
|
|
87
|
-
hostDirectives: [
|
|
90
|
+
hostDirectives: [
|
|
91
|
+
{ directive: SymbioteStyleInputDirective, inputs: ['style'] },
|
|
92
|
+
],
|
|
88
93
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
89
94
|
imports: [ViewHost, SymbioteHostPropsDirective],
|
|
90
95
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
91
96
|
// The wrapper carries a11y + onLayout; 'position' ('nested') pushes the children in an inner
|
|
92
97
|
// View by `bottom: inset`, the wrapper modes adjust the single wrapper directly. Only one @if
|
|
93
98
|
// branch is instantiated, so each branch's <ng-content> projects the children unambiguously.
|
|
99
|
+
// `layout` stays a plain, unconditional template binding: unlike the four accessibility events
|
|
100
|
+
// below, this component READS its own onLayout internally (handleLayout measures the frame that
|
|
101
|
+
// feeds the next keyboard event's inset math — see keyboard-avoiding-view-rn-contract.md), so the
|
|
102
|
+
// gate must stay lit regardless of whether the app subscribes to the `layout` @Output(). Gating it
|
|
103
|
+
// on `.observed` would silently break the inset fixpoint correction the moment an app didn't
|
|
104
|
+
// listen to `layout`.
|
|
94
105
|
template: `
|
|
95
106
|
<symbiote-view
|
|
96
107
|
[symbioteHostProps]="hostProps"
|
|
97
|
-
(accessibilityAction)="emit(accessibilityAction, $event)"
|
|
98
|
-
(accessibilityTap)="emit(accessibilityTap, $event)"
|
|
99
|
-
(magicTap)="emit(magicTap, $event)"
|
|
100
|
-
(accessibilityEscape)="emit(accessibilityEscape, $event)"
|
|
101
108
|
(layout)="handleLayout($event)"
|
|
102
109
|
>
|
|
103
110
|
@if (isNested) {
|
|
@@ -110,7 +117,9 @@ export type IAngularKeyboardAvoidingViewInputs = Omit<
|
|
|
110
117
|
</symbiote-view>
|
|
111
118
|
`,
|
|
112
119
|
})
|
|
113
|
-
export class KeyboardAvoidingView
|
|
120
|
+
export class KeyboardAvoidingView
|
|
121
|
+
implements IAngularKeyboardAvoidingViewInputs, OnInit, OnDestroy
|
|
122
|
+
{
|
|
114
123
|
@Input() behavior?: IKeyboardAvoidingBehavior;
|
|
115
124
|
@Input() enabled?: boolean;
|
|
116
125
|
@Input() keyboardVerticalOffset?: number;
|
|
@@ -135,8 +144,10 @@ export class KeyboardAvoidingView implements IAngularKeyboardAvoidingViewInputs,
|
|
|
135
144
|
@Input() accessibilityValue?: IAccessibilityProps['accessibilityValue'];
|
|
136
145
|
@Input() accessibilityActions?: IAccessibilityProps['accessibilityActions'];
|
|
137
146
|
@Input() accessibilityLabelledBy?: string | string[];
|
|
138
|
-
@Input()
|
|
139
|
-
|
|
147
|
+
@Input()
|
|
148
|
+
importantForAccessibility?: IAccessibilityProps['importantForAccessibility'];
|
|
149
|
+
@Input()
|
|
150
|
+
accessibilityLiveRegion?: IAccessibilityProps['accessibilityLiveRegion'];
|
|
140
151
|
@Input() screenReaderFocusable?: boolean;
|
|
141
152
|
@Input() accessibilityViewIsModal?: boolean;
|
|
142
153
|
@Input() accessibilityElementsHidden?: boolean;
|
|
@@ -168,6 +179,12 @@ export class KeyboardAvoidingView implements IAngularKeyboardAvoidingViewInputs,
|
|
|
168
179
|
// keyboard event's inset math (React's frameRef / initialHeightRef, Vue's frame / initialHeight).
|
|
169
180
|
private frame?: IMeasuredFrame;
|
|
170
181
|
private initialHeight?: number;
|
|
182
|
+
// The iOS "Prefer Cross-Fade Transitions" setting, read once per mount. Deliberately a plain
|
|
183
|
+
// field and NOT markForCheck'd when the promise lands: it is a device setting that cannot change
|
|
184
|
+
// mid-session, and it feeds only the NEXT keyboard event's math — never the current render — so
|
|
185
|
+
// under zoneless CD there is nothing to repaint at resolve time. The keyboard event that does use
|
|
186
|
+
// it already calls markForCheck itself.
|
|
187
|
+
private prefersCrossFadeTransitions = false;
|
|
171
188
|
private subscriptions: IEventSubscription[] = [];
|
|
172
189
|
|
|
173
190
|
private readonly changeDetector = inject(ChangeDetectorRef);
|
|
@@ -177,11 +194,21 @@ export class KeyboardAvoidingView implements IAngularKeyboardAvoidingViewInputs,
|
|
|
177
194
|
private readonly elementRef = inject(ElementRef);
|
|
178
195
|
|
|
179
196
|
ngOnInit(): void {
|
|
197
|
+
// TWO events, picked per host, never three: iOS takes the will* pair so the view rides up with
|
|
198
|
+
// the keyboard animation, Android the did* pair. The change-frame notification is deliberately
|
|
199
|
+
// absent — with an undocked/split/floating iOS keyboard it fires BEFORE the hide, so listening
|
|
200
|
+
// to it applies a frame captured mid-dismissal (RN KeyboardAvoidingView.js's own comment).
|
|
201
|
+
const events = keyboardAvoidingEventNamesFor(Platform.OS);
|
|
180
202
|
this.subscriptions = [
|
|
181
|
-
Keyboard.addListener(
|
|
182
|
-
Keyboard.addListener(
|
|
183
|
-
Keyboard.addListener(KEYBOARD_EVENT.didHide, () => this.onHide()),
|
|
203
|
+
Keyboard.addListener(events.show, payload => this.onShow(payload)),
|
|
204
|
+
Keyboard.addListener(events.hide, () => this.onHide()),
|
|
184
205
|
];
|
|
206
|
+
// The core wrapper, not AccessibilityInfo directly: the engine's iOS getter REJECTS on a
|
|
207
|
+
// native error (RN parity), and nobody awaits this read, so an unwrapped call would surface
|
|
208
|
+
// as an unhandled rejection. The wrapper answers false on a failed read.
|
|
209
|
+
void readPrefersCrossFadeTransitions().then(enabled => {
|
|
210
|
+
this.prefersCrossFadeTransitions = enabled;
|
|
211
|
+
});
|
|
185
212
|
}
|
|
186
213
|
|
|
187
214
|
ngOnDestroy(): void {
|
|
@@ -192,7 +219,17 @@ export class KeyboardAvoidingView implements IAngularKeyboardAvoidingViewInputs,
|
|
|
192
219
|
private onShow(payload: unknown): void {
|
|
193
220
|
const keyboard = readKeyboardFrame(payload);
|
|
194
221
|
const offset = this.keyboardVerticalOffset ?? DEFAULT_VERTICAL_OFFSET;
|
|
195
|
-
const next = computeInset(this.frame, keyboard, offset
|
|
222
|
+
const next = computeInset(this.frame, keyboard, offset, {
|
|
223
|
+
// Read off the field at event time, not captured when the subscription was built: `behavior`
|
|
224
|
+
// is an @Input that can change under a long-lived subscription, and a stale one keeps
|
|
225
|
+
// applying the old mode's math (it gates the previous-inset correction below).
|
|
226
|
+
behavior: this.behavior,
|
|
227
|
+
// The inset CURRENTLY applied (RN's this.state.bottom), read off the field at event time so
|
|
228
|
+
// 'height' mode's fixpoint correction sees the live value: that mode SHRINKS the wrapper by
|
|
229
|
+
// the inset, so the next onLayout reports a frame shorter by exactly that much.
|
|
230
|
+
previousInset: this.inset,
|
|
231
|
+
prefersCrossFadeTransitions: this.prefersCrossFadeTransitions,
|
|
232
|
+
});
|
|
196
233
|
dlog(`KeyboardAvoidingView show -> inset ${next}`);
|
|
197
234
|
this.inset = next;
|
|
198
235
|
this.changeDetector.markForCheck();
|
|
@@ -211,7 +248,8 @@ export class KeyboardAvoidingView implements IAngularKeyboardAvoidingViewInputs,
|
|
|
211
248
|
const measured = readLayoutFrame(event.nativeEvent.layout);
|
|
212
249
|
if (measured !== undefined) {
|
|
213
250
|
this.frame = measured;
|
|
214
|
-
if (this.initialHeight === undefined)
|
|
251
|
+
if (this.initialHeight === undefined)
|
|
252
|
+
this.initialHeight = measured.height;
|
|
215
253
|
}
|
|
216
254
|
this.layout.emit(event);
|
|
217
255
|
}
|
|
@@ -244,7 +282,9 @@ export class KeyboardAvoidingView implements IAngularKeyboardAvoidingViewInputs,
|
|
|
244
282
|
}
|
|
245
283
|
|
|
246
284
|
get innerStyle(): IStyleProp<IViewStyle> | undefined {
|
|
247
|
-
return this.resolvedLayout.kind === 'nested'
|
|
285
|
+
return this.resolvedLayout.kind === 'nested'
|
|
286
|
+
? this.resolvedLayout.innerStyle
|
|
287
|
+
: undefined;
|
|
248
288
|
}
|
|
249
289
|
|
|
250
290
|
// The wrapper's full prop bag (style + identity + a11y) folded for `[symbioteHostProps]`,
|
|
@@ -258,16 +298,28 @@ export class KeyboardAvoidingView implements IAngularKeyboardAvoidingViewInputs,
|
|
|
258
298
|
nativeID: this.nativeID,
|
|
259
299
|
accessible: this.accessible,
|
|
260
300
|
...this.folded,
|
|
301
|
+
onAccessibilityAction: this.eventEmitterHandler(this.accessibilityAction),
|
|
302
|
+
onAccessibilityTap: this.eventEmitterHandler(this.accessibilityTap),
|
|
303
|
+
onMagicTap: this.eventEmitterHandler(this.magicTap),
|
|
304
|
+
onAccessibilityEscape: this.eventEmitterHandler(this.accessibilityEscape),
|
|
261
305
|
};
|
|
262
306
|
}
|
|
263
307
|
|
|
264
308
|
// Forward an engine event to the matching @Output(), narrowing the template's untyped $event
|
|
265
|
-
// first.
|
|
266
|
-
|
|
267
|
-
emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
|
|
309
|
+
// first.
|
|
310
|
+
private emit(emitter: EventEmitter<ISymbioteEvent>, event: unknown): void {
|
|
268
311
|
if (isSymbioteEvent(event)) emitter.emit(event);
|
|
269
312
|
}
|
|
270
313
|
|
|
314
|
+
// The four accessibility events are boolean-GATED Fabric events
|
|
315
|
+
// (`.claude/rules/fabric-boolean-event-gates.md`). `.observed`-gated, unlike `layout` above which
|
|
316
|
+
// this component needs unconditionally for its own internal frame measurement.
|
|
317
|
+
private eventEmitterHandler(
|
|
318
|
+
emitter: EventEmitter<ISymbioteEvent>,
|
|
319
|
+
): ((event: unknown) => void) | undefined {
|
|
320
|
+
return emitter.observed ? event => this.emit(emitter, event) : undefined;
|
|
321
|
+
}
|
|
322
|
+
|
|
271
323
|
// Fold the web aria-*/role aliases into the canonical accessibility* props once per render, so the
|
|
272
324
|
// host node never sees an aria-* key (native ignores them) — the shared transform every adapter runs.
|
|
273
325
|
get folded(): Partial<IAngularKeyboardAvoidingViewProps> {
|
|
@@ -286,8 +338,10 @@ export class KeyboardAvoidingView implements IAngularKeyboardAvoidingViewInputs,
|
|
|
286
338
|
accessibilityElementsHidden: this.accessibilityElementsHidden,
|
|
287
339
|
accessibilityIgnoresInvertColors: this.accessibilityIgnoresInvertColors,
|
|
288
340
|
accessibilityLanguage: this.accessibilityLanguage,
|
|
289
|
-
accessibilityRespondsToUserInteraction:
|
|
290
|
-
|
|
341
|
+
accessibilityRespondsToUserInteraction:
|
|
342
|
+
this.accessibilityRespondsToUserInteraction,
|
|
343
|
+
accessibilityShowsLargeContentViewer:
|
|
344
|
+
this.accessibilityShowsLargeContentViewer,
|
|
291
345
|
accessibilityLargeContentTitle: this.accessibilityLargeContentTitle,
|
|
292
346
|
role: this.role,
|
|
293
347
|
'aria-label': this.ariaLabel,
|