@symbiote-native/angular 3.0.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/angular/callback-host.d.ts +2 -5
- package/build/angular/callback-host.js +11 -36
- package/build/angular/callback-host.js.map +1 -1
- package/build/angular/change-detection-flush.d.ts +1 -0
- package/build/angular/change-detection-flush.js +58 -14
- package/build/angular/change-detection-flush.js.map +1 -1
- package/build/angular/components/flat-list/index.d.ts +4 -2
- package/build/angular/components/flat-list/index.js +28 -78
- package/build/angular/components/flat-list/index.js.map +1 -1
- package/build/angular/components/image-shared.d.ts +8 -2
- package/build/angular/components/modal/index.d.ts +1 -0
- package/build/angular/components/modal/index.js +23 -16
- package/build/angular/components/modal/index.js.map +1 -1
- package/build/angular/components/section-list/index.d.ts +3 -1
- package/build/angular/components/section-list/index.js +10 -2
- package/build/angular/components/section-list/index.js.map +1 -1
- package/build/angular/components/virtualized-list/directives.js +7 -4
- package/build/angular/components/virtualized-list/directives.js.map +1 -1
- package/build/angular/components/virtualized-list/index.d.ts +10 -2
- package/build/angular/components/virtualized-list/index.js +45 -25
- package/build/angular/components/virtualized-list/index.js.map +1 -1
- package/build/angular/components/virtualized-section-list/index.d.ts +5 -1
- package/build/angular/components/virtualized-section-list/index.js +10 -2
- package/build/angular/components/virtualized-section-list/index.js.map +1 -1
- package/build/angular/descriptor-to-angular/index.js +6 -3
- package/build/angular/descriptor-to-angular/index.js.map +1 -1
- package/build/angular/element-props.d.ts +16 -5
- package/build/angular/elements.d.ts +5 -21
- package/build/angular/elements.js +18 -26
- package/build/angular/elements.js.map +1 -1
- package/build/angular/index.d.ts +0 -1
- package/build/angular/index.js +5 -7
- package/build/angular/index.js.map +1 -1
- package/build/angular/primitives/shared.js +5 -1
- package/build/angular/primitives/shared.js.map +1 -1
- package/build/angular/renderer/index.d.ts +11 -0
- package/build/angular/renderer/index.js +97 -12
- package/build/angular/renderer/index.js.map +1 -1
- package/build/angular/runtime-matching.d.ts +0 -1
- package/build/angular/runtime-matching.js +6 -29
- package/build/angular/runtime-matching.js.map +1 -1
- package/package.json +6 -6
- package/src/callback-host.ts +10 -43
- package/src/change-detection-flush.ts +67 -14
- package/src/components/flat-list/index.ts +22 -54
- package/src/components/modal/index.ts +22 -16
- package/src/components/section-list/index.ts +4 -0
- package/src/components/virtualized-list/directives.ts +10 -8
- package/src/components/virtualized-list/index.ts +38 -13
- package/src/components/virtualized-section-list/index.ts +6 -0
- package/src/descriptor-to-angular/index.ts +6 -3
- package/src/element-props.ts +22 -13
- package/src/elements.ts +21 -56
- package/src/index.ts +5 -7
- package/src/primitives/shared.ts +5 -1
- package/src/renderer/index.ts +107 -11
- package/src/runtime-matching.ts +6 -30
- package/build/angular/style-host.d.ts +0 -11
- package/build/angular/style-host.js +0 -86
- package/build/angular/style-host.js.map +0 -1
- package/src/style-host.ts +0 -94
package/src/elements.ts
CHANGED
|
@@ -58,7 +58,6 @@ import {
|
|
|
58
58
|
} from './change-detection-flush';
|
|
59
59
|
import { SymbioteCallbackHost } from './callback-host';
|
|
60
60
|
import { withholdFromRuntimeMatching } from './runtime-matching';
|
|
61
|
-
import { SymbioteStyleHost } from './style-host';
|
|
62
61
|
import type {
|
|
63
62
|
IActivityIndicatorProps,
|
|
64
63
|
IImageProps,
|
|
@@ -190,44 +189,21 @@ export abstract class SymbioteElement implements OnChanges {
|
|
|
190
189
|
@Input() shouldRasterizeIOS?: IElementProps['shouldRasterizeIOS'];
|
|
191
190
|
@Input()
|
|
192
191
|
needsOffscreenAlphaCompositing?: IElementProps['needsOffscreenAlphaCompositing'];
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
// in examples/angular painted as an empty bordered box, because the throw aborted the update
|
|
200
|
-
// before its `<text>` child was ever reached.
|
|
201
|
-
//
|
|
202
|
-
// A declared input CLAIMS the binding at compile time, so it never reaches the styling engine —
|
|
203
|
-
// the same mechanism the primitive host COMPONENTS have always relied on
|
|
204
|
-
// (`primitives/shared.ts`), which is why `<Pressable [style]>` worked and its replacement tag did
|
|
205
|
-
// not. `[style.borderWidth.px]` is a different instruction (`ɵɵstyleProp`) and is untouched: it
|
|
206
|
-
// still reaches `Renderer2.setStyle`, which merges per key.
|
|
192
|
+
// `[style]` and `[class]` are declared for the TYPE CHECK only. Withheld from run-time matching, no
|
|
193
|
+
// input claims them, so Angular's own styling engine decomposes them into `setStyle`/`addClass`
|
|
194
|
+
// exactly as on a DOM element - which is why `style` is typed as the object/string that engine can
|
|
195
|
+
// hold. An RN style ARRAY or press-state FUNCTION throws in there ("indexOf is not a function",
|
|
196
|
+
// "Unsupported styling type: function"), so it is `[styleProp]`: an ordinary property binding the
|
|
197
|
+
// renderer routes to `style` whole. A claim would cost a directive instance per element.
|
|
207
198
|
//
|
|
208
199
|
// The press-state callback rides the BASE type rather than `PressableElement` alone: a subclass
|
|
209
200
|
// cannot widen an inherited property, and only the pressables have a `pressed` to read — so on
|
|
210
|
-
// any other tag the engine resolves it at `pressed: false
|
|
211
|
-
// in what commits.
|
|
201
|
+
// any other tag the engine resolves it at `pressed: false`.
|
|
212
202
|
@Input() style?: IElementProps['style'];
|
|
203
|
+
@Input() styleProp?: IElementProps['styleProp'];
|
|
213
204
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
*
|
|
217
|
-
* A styling binding goes to a directive input when the directive declares that exact public name
|
|
218
|
-
* — `setShadowStylingInputFlags` sets `hasClassInput`/`hasStyleInput` off the input map, and
|
|
219
|
-
* `checkStylingMap` then hands the whole value over and never calls the renderer per key
|
|
220
|
-
* (`view/directives.ts`, `instructions/styling.ts`). `style` has been declared here all along and
|
|
221
|
-
* is shadowed; `class` was not, so every `[class]` reached the renderer one TOKEN at a time — on
|
|
222
|
-
* the element-directive path as much as the bare one. Measured at ~3.3 us per class binding
|
|
223
|
-
* (`adapter-create-cost.itest.ts`, "prices the class channel"), on the channel every example app
|
|
224
|
-
* uses for its static look.
|
|
225
|
-
*
|
|
226
|
-
* `[class.foo]` and `[ngClass]` are NOT shadowed and keep arriving as `addClass`, so a node can
|
|
227
|
-
* now be told its classes both ways at once; the renderer unions the two sources
|
|
228
|
-
* (`classStringFor`). Typed as a string rather than RN's `className`, because that is what
|
|
229
|
-
* `ɵɵclassMap` hands over after it concatenates any static `class=` prefix.
|
|
230
|
-
*/
|
|
205
|
+
// `ɵɵclassMap` hands over a string after joining any static `class=` prefix; `[class.foo]` and
|
|
206
|
+
// `[ngClass]` arrive as `addClass`. The renderer unions both sources (`classStringFor`).
|
|
231
207
|
@Input() class?: string;
|
|
232
208
|
|
|
233
209
|
// The flat-bag spelling of the events below. `(press)` and `[onPress]` are both supported and
|
|
@@ -422,6 +398,7 @@ export class TextElement extends SymbioteElement {
|
|
|
422
398
|
// initializer is what TS2612 asks for to accept a redeclaration as deliberate; `declare` would
|
|
423
399
|
// be the other answer and cannot carry a decorator.
|
|
424
400
|
@Input() override style?: ITextElementProps['style'] = undefined;
|
|
401
|
+
@Input() override styleProp?: ITextElementProps['styleProp'] = undefined;
|
|
425
402
|
@Input() numberOfLines?: ITextElementProps['numberOfLines'];
|
|
426
403
|
@Input() ellipsizeMode?: ITextElementProps['ellipsizeMode'];
|
|
427
404
|
@Input() selectable?: ITextElementProps['selectable'];
|
|
@@ -927,39 +904,27 @@ export const SYMBIOTE_ELEMENTS = [
|
|
|
927
904
|
// bind one and carries the `ChangeDetectorRef` their wrapper needs. It rides this list for the same
|
|
928
905
|
// reason the accessors do, and `elements.test.ts` subtracts it from the tag-coverage check by name.
|
|
929
906
|
SymbioteCallbackHost,
|
|
930
|
-
// The other attribute-matched one: it claims `[style]` and `[class]` so an RN style ARRAY never
|
|
931
|
-
// reaches Angular's styling engine, which cannot represent one and throws. See `style-host.ts`.
|
|
932
|
-
SymbioteStyleHost,
|
|
933
907
|
] as const;
|
|
934
908
|
|
|
935
909
|
// THE DIRECTIVES THAT GO ON MATCHING, and the list is the exceptions rather than the rule.
|
|
936
910
|
//
|
|
937
|
-
//
|
|
938
|
-
//
|
|
939
|
-
//
|
|
940
|
-
// check at ~8.5-9.4 us of run time per element, and `./runtime-matching` keeps the check while
|
|
941
|
-
// dropping the instance.
|
|
942
|
-
//
|
|
943
|
-
// These four cannot go, because they DO something when they are built:
|
|
911
|
+
// EVERY TAG DIRECTIVE IS WITHHELD. Each is `@Input()` declarations over one inherited `ngOnChanges`
|
|
912
|
+
// forwarding to the renderer (the call `ɵɵproperty` makes directly on an unclaimed element), so the
|
|
913
|
+
// instance bought a compile-time check at ~8 us of run time per element.
|
|
944
914
|
//
|
|
945
|
-
//
|
|
946
|
-
//
|
|
947
|
-
//
|
|
948
|
-
// refresh-control `ReadBackElement`, the same flush for the same reason
|
|
915
|
+
// The read-back tags (text-input, text-input-multiline, switch, refresh-control) need a synchronous
|
|
916
|
+
// view flush; the renderer marks them at creation and the flush finds their view lazily
|
|
917
|
+
// (`change-detection-flush.ts`). `[(value)]` reaches the renderer's `listen('valueChange')` bridge.
|
|
949
918
|
//
|
|
950
|
-
//
|
|
951
|
-
//
|
|
919
|
+
// `[style]`/`[class]` stay unclaimed as on a DOM element (see `SymbioteElement.style`). What still
|
|
920
|
+
// matches is selected on an ATTRIBUTE an app writes: the two form accessors and
|
|
921
|
+
// `SymbioteCallbackHost`.
|
|
952
922
|
withholdFromRuntimeMatching(
|
|
953
923
|
SYMBIOTE_ELEMENTS.filter(
|
|
954
924
|
directive =>
|
|
955
|
-
directive !== TextInputElement &&
|
|
956
|
-
directive !== MultilineTextInputElement &&
|
|
957
|
-
directive !== SwitchElement &&
|
|
958
|
-
directive !== RefreshControlElement &&
|
|
959
925
|
directive !== TextInputValueAccessor &&
|
|
960
926
|
directive !== SwitchValueAccessor &&
|
|
961
|
-
directive !== SymbioteCallbackHost
|
|
962
|
-
directive !== SymbioteStyleHost,
|
|
927
|
+
directive !== SymbioteCallbackHost,
|
|
963
928
|
),
|
|
964
929
|
);
|
|
965
930
|
|
package/src/index.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// `@Component`s on the `view` and `text` SELECTORS — a second mechanism on the same tags
|
|
10
10
|
// `SYMBIOTE_ELEMENTS` covers, kept for one stated reason their own header gives: "Declaring `style`
|
|
11
11
|
// as a real Angular input prevents Angular's CSS style engine from decomposing RN `StyleProp`
|
|
12
|
-
// arrays".
|
|
13
|
-
//
|
|
12
|
+
// arrays". A tag takes an array or press-state callback as `[styleProp]`, a plain property binding
|
|
13
|
+
// with no instance behind it.
|
|
14
14
|
//
|
|
15
15
|
// An app loses nothing it cannot spell better: `@ViewChild('ref')` on a template reference returns
|
|
16
16
|
// Angular's own `ElementRef<IHostInstance>`, whose `nativeElement` IS the engine host node — the same
|
|
@@ -146,10 +146,9 @@ export { setImageSourceResolver } from './components';
|
|
|
146
146
|
//
|
|
147
147
|
// They were removed on 2026-09-18 and put back the same hour. The reason to remove them is real: a
|
|
148
148
|
// withheld tag directive (`./runtime-matching`) is a compile-time declaration and nothing else, so
|
|
149
|
-
// what makes `<view [
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
// and then fails on a device the first time the app writes an array style.
|
|
149
|
+
// what makes `<view [onPress]="fn">` work at all is `SymbioteCallbackHost` claiming the name before
|
|
150
|
+
// `setDomProperty` throws NG0306. It rides this array, so a narrow `imports: [ViewElement]`
|
|
151
|
+
// type-checks and then fails on a device the first time the app binds an `on*` prop.
|
|
153
152
|
//
|
|
154
153
|
// WHAT PUTS THEM BACK is NG3004: `Unable to import directive ViewElement — the symbol is not
|
|
155
154
|
// exported from index.d.ts`. ngtsc resolves every directive reachable through an imported array to an
|
|
@@ -198,7 +197,6 @@ export {
|
|
|
198
197
|
CALLBACK_ATTRIBUTE_SELECTOR,
|
|
199
198
|
SymbioteCallbackHost,
|
|
200
199
|
} from './callback-host';
|
|
201
|
-
export { STYLE_HOST_SELECTOR, SymbioteStyleHost } from './style-host';
|
|
202
200
|
export type {
|
|
203
201
|
IElementProps,
|
|
204
202
|
IStickyHeaderElementProps,
|
package/src/primitives/shared.ts
CHANGED
|
@@ -298,7 +298,11 @@ export class SymbioteHostPropsDirective {
|
|
|
298
298
|
// Only keys the node actually carries, so the vanished-key sweep below walks a handful rather
|
|
299
299
|
// than the bag's whole declared shape.
|
|
300
300
|
const next: Record<string, unknown> = {};
|
|
301
|
-
|
|
301
|
+
// `Object.keys` rather than `Object.entries`: `entries` allocates a two-element array per key
|
|
302
|
+
// before the loop starts, and this walk runs once per element instance. Measured on `-O` Hermes
|
|
303
|
+
// (`object-iteration-cost.itest.ts`), 0.19 us per node over a four-key bag.
|
|
304
|
+
for (const key of Object.keys(props)) {
|
|
305
|
+
const value = props[key];
|
|
302
306
|
if (value !== undefined) next[key] = value;
|
|
303
307
|
if (Object.is(pushed[key], value)) continue;
|
|
304
308
|
this.renderer.setProperty(
|
package/src/renderer/index.ts
CHANGED
|
@@ -81,9 +81,23 @@ import {
|
|
|
81
81
|
createCallbackWrapper,
|
|
82
82
|
flushViewFor,
|
|
83
83
|
isWrappableCallback,
|
|
84
|
+
markReadBackNode,
|
|
84
85
|
type ICallbackWrapper,
|
|
85
86
|
} from '../change-detection-flush';
|
|
86
87
|
|
|
88
|
+
// The tags whose behavior reads the app's answer back inside the event's own turn - the three
|
|
89
|
+
// behaviors `../change-detection-flush` names, plus the multiline spelling of the input. Marked at
|
|
90
|
+
// creation so a flush finds their view lazily instead of a directive instance per element.
|
|
91
|
+
const READ_BACK_TAGS: ReadonlySet<string> = new Set([
|
|
92
|
+
'text-input',
|
|
93
|
+
'text-input-multiline',
|
|
94
|
+
'switch',
|
|
95
|
+
'refresh-control',
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
// The property binding an RN StyleProp travels as; see `SymbioteElement.style`.
|
|
99
|
+
const STYLE_PROP_BINDING = 'styleProp';
|
|
100
|
+
|
|
87
101
|
// The app callbacks an engine behavior READS BACK inside the same microtask turn, as an Angular
|
|
88
102
|
// `(event)` binding — `valueChange` is handled in `listen` on its own, since it also needs the field
|
|
89
103
|
// unwrapped. Zoneless change detection is a macrotask, so without a flush the behavior reads the
|
|
@@ -253,6 +267,13 @@ export class SymbioteRenderer implements Renderer2 {
|
|
|
253
267
|
// free of a second guard.
|
|
254
268
|
private pendingStyleNode: ISymbioteNode | undefined;
|
|
255
269
|
private pendingStyle: Record<string, unknown> = {};
|
|
270
|
+
// A node with no standing style is matched against a published style key by key, as Angular hands
|
|
271
|
+
// the keys over, and builds nothing while they match. Rows sharing a style then skip the
|
|
272
|
+
// accumulator and the shallow compare's two key arrays (~320 B a `view` on create). The first
|
|
273
|
+
// mismatch materializes the accumulator from the keys matched so far.
|
|
274
|
+
private matchCandidate: Record<string, unknown> | undefined;
|
|
275
|
+
private matchCandidateSize = 0;
|
|
276
|
+
private readonly matchedKeys: string[] = [];
|
|
256
277
|
private pendingClassNode: ISymbioteNode | undefined;
|
|
257
278
|
private readonly releaseBeforeFlush: () => void;
|
|
258
279
|
|
|
@@ -303,10 +324,20 @@ export class SymbioteRenderer implements Renderer2 {
|
|
|
303
324
|
private flushStyling(): void {
|
|
304
325
|
const styled = this.pendingStyleNode;
|
|
305
326
|
if (styled !== undefined) {
|
|
306
|
-
const style = this.pendingStyle;
|
|
307
327
|
this.pendingStyleNode = undefined;
|
|
308
|
-
|
|
309
|
-
|
|
328
|
+
const candidate = this.matchCandidate;
|
|
329
|
+
if (
|
|
330
|
+
candidate !== undefined &&
|
|
331
|
+
this.matchedKeys.length === this.matchCandidateSize
|
|
332
|
+
) {
|
|
333
|
+
this.matchCandidate = undefined;
|
|
334
|
+
routeProp(styled, 'style', candidate);
|
|
335
|
+
} else {
|
|
336
|
+
this.materializeMatch();
|
|
337
|
+
const style = this.pendingStyle;
|
|
338
|
+
this.pendingStyle = {};
|
|
339
|
+
routeProp(styled, 'style', this.canonicalStyle(style));
|
|
340
|
+
}
|
|
310
341
|
}
|
|
311
342
|
const classed = this.pendingClassNode;
|
|
312
343
|
if (classed !== undefined) {
|
|
@@ -362,6 +393,8 @@ export class SymbioteRenderer implements Renderer2 {
|
|
|
362
393
|
* this simply stops sharing — it never stops being correct.
|
|
363
394
|
*/
|
|
364
395
|
private readonly publishedStyles: Record<string, unknown>[] = [];
|
|
396
|
+
// Key count of each entry above, index for index: how a key-by-key match knows it is complete.
|
|
397
|
+
private readonly publishedSizes: number[] = [];
|
|
365
398
|
|
|
366
399
|
/** A published object equal to this one, or this one — which then becomes the published copy. */
|
|
367
400
|
private canonicalStyle(
|
|
@@ -373,26 +406,84 @@ export class SymbioteRenderer implements Renderer2 {
|
|
|
373
406
|
if (at > 0) {
|
|
374
407
|
this.publishedStyles.splice(at, 1);
|
|
375
408
|
this.publishedStyles.unshift(known);
|
|
409
|
+
this.publishedSizes.unshift(this.publishedSizes.splice(at, 1)[0]);
|
|
376
410
|
}
|
|
377
411
|
return known;
|
|
378
412
|
}
|
|
379
413
|
this.publishedStyles.unshift(style);
|
|
380
|
-
|
|
414
|
+
this.publishedSizes.unshift(Object.keys(style).length);
|
|
415
|
+
if (this.publishedStyles.length > STYLE_CACHE) {
|
|
416
|
+
this.publishedStyles.pop();
|
|
417
|
+
this.publishedSizes.pop();
|
|
418
|
+
}
|
|
381
419
|
return style;
|
|
382
420
|
}
|
|
383
421
|
|
|
384
|
-
/**
|
|
385
|
-
private
|
|
386
|
-
if (this.pendingStyleNode === el) return
|
|
422
|
+
/** Open this node's style run (closing any other); a no-op when it is already the open one. */
|
|
423
|
+
private openRun(el: ISymbioteNode): void {
|
|
424
|
+
if (this.pendingStyleNode === el) return;
|
|
387
425
|
this.flushStyling();
|
|
426
|
+
this.pendingStyleNode = el;
|
|
388
427
|
// Seeded from what is STANDING, because Angular sends only the keys that changed — an update
|
|
389
428
|
// that moves one key must not drop the rest.
|
|
390
429
|
const current = getExplicitStyle(el);
|
|
391
|
-
|
|
392
|
-
|
|
430
|
+
if (isRecord(current)) {
|
|
431
|
+
this.pendingStyle = { ...current };
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
this.isChoosingCandidate = true;
|
|
435
|
+
this.matchedKeys.length = 0;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// The FIRST key picks the candidate: a row alternates styles (row, cell, cell, input), so the
|
|
439
|
+
// front entry is usually the neighbour's.
|
|
440
|
+
private isChoosingCandidate = false;
|
|
441
|
+
|
|
442
|
+
private chooseCandidate(key: string, value: unknown): void {
|
|
443
|
+
this.isChoosingCandidate = false;
|
|
444
|
+
if (value === undefined) return;
|
|
445
|
+
for (let at = 0; at < this.publishedStyles.length; at += 1) {
|
|
446
|
+
const known = this.publishedStyles[at];
|
|
447
|
+
if (!Object.is(known[key], value)) continue;
|
|
448
|
+
this.matchCandidate = known;
|
|
449
|
+
this.matchCandidateSize = this.publishedSizes[at] ?? 0;
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** Stop matching: write the keys matched so far into the accumulator the run goes on with. */
|
|
455
|
+
private materializeMatch(): void {
|
|
456
|
+
this.isChoosingCandidate = false;
|
|
457
|
+
const candidate = this.matchCandidate;
|
|
458
|
+
if (candidate === undefined) return;
|
|
459
|
+
this.matchCandidate = undefined;
|
|
460
|
+
for (const key of this.matchedKeys) this.pendingStyle[key] = candidate[key];
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** The accumulator for this node's style run, opening one (and closing any other) if needed. */
|
|
464
|
+
private openStyleRun(el: ISymbioteNode): Record<string, unknown> {
|
|
465
|
+
this.openRun(el);
|
|
466
|
+
this.materializeMatch();
|
|
393
467
|
return this.pendingStyle;
|
|
394
468
|
}
|
|
395
469
|
|
|
470
|
+
private writeStyle(el: ISymbioteNode, key: string, value: unknown): void {
|
|
471
|
+
this.openRun(el);
|
|
472
|
+
if (this.isChoosingCandidate) this.chooseCandidate(key, value);
|
|
473
|
+
const candidate = this.matchCandidate;
|
|
474
|
+
if (
|
|
475
|
+
candidate !== undefined &&
|
|
476
|
+
value !== undefined &&
|
|
477
|
+
Object.is(candidate[key], value) &&
|
|
478
|
+
!this.matchedKeys.includes(key)
|
|
479
|
+
) {
|
|
480
|
+
this.matchedKeys.push(key);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
this.materializeMatch();
|
|
484
|
+
this.pendingStyle[key] = value;
|
|
485
|
+
}
|
|
486
|
+
|
|
396
487
|
createElement(name: string): IHostNode {
|
|
397
488
|
// `name` is the component's host tag — a symbiote intrinsic (`view`,
|
|
398
489
|
// `text`, …), a public ergonomic alias (`View`, `Text`), or a raw Fabric view
|
|
@@ -442,6 +533,7 @@ export class SymbioteRenderer implements Renderer2 {
|
|
|
442
533
|
if (isDebug()) {
|
|
443
534
|
dlog(`angular createElement ${name} -> ${descriptor.component}`);
|
|
444
535
|
}
|
|
536
|
+
if (READ_BACK_TAGS.has(engineName)) markReadBackNode(node);
|
|
445
537
|
return toPublicInstance(node);
|
|
446
538
|
}
|
|
447
539
|
|
|
@@ -653,7 +745,7 @@ export class SymbioteRenderer implements Renderer2 {
|
|
|
653
745
|
if (isSurface(el)) return;
|
|
654
746
|
countAngular('rendererWrites');
|
|
655
747
|
noteAngularStyleWrite(style);
|
|
656
|
-
this.
|
|
748
|
+
this.writeStyle(el, style, value);
|
|
657
749
|
this.surface.requestCommit();
|
|
658
750
|
}
|
|
659
751
|
|
|
@@ -709,7 +801,11 @@ export class SymbioteRenderer implements Renderer2 {
|
|
|
709
801
|
}
|
|
710
802
|
}
|
|
711
803
|
this.flushStyling();
|
|
712
|
-
|
|
804
|
+
// `[styleProp]` IS `style`, carried as a plain property so an RN array or press-state callback
|
|
805
|
+
// reaches the engine whole instead of Angular's styling engine. Binding it beside `[style]` on one
|
|
806
|
+
// element makes the later write win - they are one prop.
|
|
807
|
+
const propName = name === STYLE_PROP_BINDING ? 'style' : name;
|
|
808
|
+
routeProp(el, propName, this.wrapCallback(el, propName, value));
|
|
713
809
|
this.surface.requestCommit();
|
|
714
810
|
}
|
|
715
811
|
|
package/src/runtime-matching.ts
CHANGED
|
@@ -27,15 +27,13 @@
|
|
|
27
27
|
// something marks the view. That moved to `SymbioteRenderer.setProperty`, which
|
|
28
28
|
// every path reaches whether a directive claimed the binding or not.
|
|
29
29
|
// the style shadow `[style]` and `[class]` are no longer claimed by an input, so `ɵɵstyleMap` and
|
|
30
|
-
// `ɵɵclassMap` resolve them per KEY
|
|
31
|
-
//
|
|
32
|
-
//
|
|
30
|
+
// `ɵɵclassMap` resolve them per KEY, as on a DOM element. An RN array or
|
|
31
|
+
// press-state callback travels as `[styleProp]` instead (`SymbioteElement.style`).
|
|
32
|
+
// the view flush the read-back tags' synchronous re-check finds the node's view lazily
|
|
33
|
+
// (`change-detection-flush.ts`) instead of injecting a `ChangeDetectorRef`.
|
|
33
34
|
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// the engine's value event and register a view flush) and `refresh-control`. `SymbioteCallbackHost`
|
|
37
|
-
// and the two form accessors match on ATTRIBUTES rather than on a tag, so they land only where they
|
|
38
|
-
// are needed and are never withheld.
|
|
35
|
+
// Every tag directive is withheld. `SymbioteCallbackHost` and the two form accessors match on
|
|
36
|
+
// ATTRIBUTES rather than on a tag, so they land only where they are needed and are never withheld.
|
|
39
37
|
|
|
40
38
|
/**
|
|
41
39
|
* Take these directives out of Angular's runtime matcher, leaving ngtsc's view of them untouched.
|
|
@@ -51,30 +49,8 @@ export function withholdFromRuntimeMatching(types: readonly unknown[]): void {
|
|
|
51
49
|
if (typeof definition !== 'object' || definition === null) continue;
|
|
52
50
|
const selectors: unknown = Reflect.get(definition, 'selectors');
|
|
53
51
|
if (!Array.isArray(selectors)) continue;
|
|
54
|
-
// Ivy holds a `CssSelectorList` — one array per alternative, whose first entry is the tag name
|
|
55
|
-
// when the alternative names one. Recorded before the emptying, because afterwards there is
|
|
56
|
-
// nothing to read and the guard below would have no subject.
|
|
57
|
-
for (const alternative of selectors)
|
|
58
|
-
if (Array.isArray(alternative)) {
|
|
59
|
-
const tag: unknown = alternative[0];
|
|
60
|
-
if (typeof tag === 'string' && tag.length > 0) withheldTags.add(tag);
|
|
61
|
-
}
|
|
62
52
|
// Emptied in place rather than replaced: the def holds this array, and reading `ɵdir` under JIT
|
|
63
53
|
// is what compiles the directive, so the object in hand is the one the matcher will walk.
|
|
64
54
|
selectors.length = 0;
|
|
65
55
|
}
|
|
66
56
|
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Every TAG that lost its directive, for the guard that keeps `SymbioteStyleHost` covering them.
|
|
70
|
-
*
|
|
71
|
-
* A withheld tag whose style is claimed by nothing sends an RN style ARRAY into Angular's styling
|
|
72
|
-
* engine, which throws inside change detection — a device failure with no compile-time tell. So the
|
|
73
|
-
* set is recorded here at the moment it is emptied rather than maintained as a second list, and
|
|
74
|
-
* `style-host-covers-withheld-tags.test.ts` reads it.
|
|
75
|
-
*/
|
|
76
|
-
const withheldTags = new Set<string>();
|
|
77
|
-
|
|
78
|
-
export function tagsWithheldFromRuntimeMatching(): ReadonlySet<string> {
|
|
79
|
-
return withheldTags;
|
|
80
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { type OnChanges, type SimpleChanges } from '@angular/core';
|
|
2
|
-
import * as i0 from "@angular/core";
|
|
3
|
-
/** Exported for the guard, which derives the same set from the withheld directives' own selectors. */
|
|
4
|
-
export declare const STYLE_HOST_SELECTOR: string;
|
|
5
|
-
export declare class SymbioteStyleHost implements OnChanges {
|
|
6
|
-
private readonly renderer;
|
|
7
|
-
private readonly host;
|
|
8
|
-
ngOnChanges(changes: SimpleChanges): void;
|
|
9
|
-
static ɵfac: i0.ɵɵFactoryDeclaration<SymbioteStyleHost, never>;
|
|
10
|
-
static ɵdir: i0.ɵɵDirectiveDeclaration<SymbioteStyleHost, "view,symbiote-view,text,symbiote-text,image,symbiote-image,image-background,pressable,symbiote-pressable,button,symbiote-button,touchable-opacity,touchable-highlight,touchable-native-feedback,touchable-without-feedback,scroll-view,horizontal-scroll-view,scroll-content,horizontal-scroll-content,activity-indicator,activity-indicator-spinner,safe-area-view,modal,symbiote-modal,sticky-header,input-accessory-view", never, { "style": { "alias": "style"; "required": false; }; "class": { "alias": "class"; "required": false; }; }, {}, never, never, true, never>;
|
|
11
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
// The directive that claims `[style]` and `[class]` on the elements that bind them.
|
|
2
|
-
//
|
|
3
|
-
// IT IS A CORRECTNESS REQUIREMENT BEFORE IT IS AN OPTIMISATION, which is what separates it from
|
|
4
|
-
// `callback-host.ts`. An RN `StyleProp` is allowed to be an ARRAY, and Angular's own styling engine
|
|
5
|
-
// cannot represent one: `ɵɵstyleMap` decomposes the value key by key, so `applyStyling` takes each
|
|
6
|
-
// array MEMBER as a style key and throws inside change detection. Device-diagnosed 2026-09-02 on
|
|
7
|
-
// ImageBackground, and pinned ever since by `renderer/style-input.test.ts`.
|
|
8
|
-
//
|
|
9
|
-
// A declared input is what keeps the value away from that engine — `checkStylingMap` hands the whole
|
|
10
|
-
// value to the input instead of walking it (`render3/instructions/styling.ts:259-288`). So when the
|
|
11
|
-
// tag directives were withheld from runtime matching, the claim had to come from somewhere, or an
|
|
12
|
-
// array style would throw on every screen that writes one.
|
|
13
|
-
//
|
|
14
|
-
// AND IT IS ALSO THE FASTER PATH, which is why `class` rides the same selector. Unclaimed, a style
|
|
15
|
-
// object reaches the renderer once per KEY — measured at ~7 000 renderer calls on the thousand-row
|
|
16
|
-
// bench row against 4 000 when an input claims it.
|
|
17
|
-
//
|
|
18
|
-
// MATCHED ON THE TAG, and the first spelling of this directive matched on `[style],[class]` instead
|
|
19
|
-
// — which does not work, for a reason worth keeping because it is invisible from the outside.
|
|
20
|
-
//
|
|
21
|
-
// `[style]` IS NOT A PROPERTY BINDING. The compiler routes it to `ɵɵstyleMap`, a styling instruction,
|
|
22
|
-
// and the name never lands in the `AttributeMarker.Bindings` run that `findAttrIndexInNode` walks
|
|
23
|
-
// (upstream `node_selector_matcher.ts:240-282`, where `Classes` and `Styles` markers are SKIPPED
|
|
24
|
-
// outright). So a directive selected on `[style]` never matches an element that binds one, and the
|
|
25
|
-
// value goes to the styling engine exactly as if no directive existed. Measured: the AOT fixture in
|
|
26
|
-
// `style-input-aot.test.ts` imports `SYMBIOTE_ELEMENTS`, carries this directive, and still threw
|
|
27
|
-
// `Unsupported styling type: function`. `[onPress]` and every other `on*` name DO work that way,
|
|
28
|
-
// which is why `callback-host.ts` can be attribute-matched and this one cannot.
|
|
29
|
-
//
|
|
30
|
-
// SO IT IS A TAG DIRECTIVE, and it is instantiated per element like the ones it replaces — including
|
|
31
|
-
// on elements that bind no style at all, because a tag selector is the only one that can match. What
|
|
32
|
-
// is bought is its SHAPE: two inputs and two injections against the withheld classes' 279 and three.
|
|
33
|
-
//
|
|
34
|
-
// MEASURED, AND SMALLER THAN THE IDEA PROMISED. On the JavaScriptCore ladder, withholding a directive
|
|
35
|
-
// outright is worth ~70-91 ms over ten thousand elements (`WITHHOLDING it`, 7.0-9.1 us each) — but
|
|
36
|
-
// the adapter cannot take that, because this directive has to stay. What it reads against a bare tag
|
|
37
|
-
// AFTER the change is 58.2 / 58.8 / 71.0 ms, against `a directive at all` at 50.2 / 55.9 / 68.3 in
|
|
38
|
-
// the same runs. Those two are the same number: a directive costs what a directive costs, thin or
|
|
39
|
-
// fat, and nearly all of it is matching and instantiating rather than inputs or injections.
|
|
40
|
-
//
|
|
41
|
-
// So the change is worth the FAT-TO-THIN difference and not the whole crossing — the ladder's own
|
|
42
|
-
// `279 inputs, not 1` (~19.9 ms) plus `the ChangeDetectorRef` (~10-31 ms). Recorded at that size
|
|
43
|
-
// rather than at the one the plan was aimed at.
|
|
44
|
-
//
|
|
45
|
-
// NO `ChangeDetectorRef` HERE. A style write is an ordinary prop write and marks nothing; the view
|
|
46
|
-
// marking belongs to the callback path and lives with it.
|
|
47
|
-
//
|
|
48
|
-
// THE FOUR TAGS THAT KEEP THEIR OWN DIRECTIVE ARE ABSENT from the selector — `text-input`,
|
|
49
|
-
// `text-input-multiline`, `switch` and `refresh-control`. Their fat directive still matches and
|
|
50
|
-
// already declares `style`, and two directives claiming one input both receive it and both forward
|
|
51
|
-
// it, which is the `unchanged` double write this adapter has measured before.
|
|
52
|
-
import { Directive, ElementRef, Renderer2, inject, } from '@angular/core';
|
|
53
|
-
import * as i0 from "@angular/core";
|
|
54
|
-
/** Exported for the guard, which derives the same set from the withheld directives' own selectors. */
|
|
55
|
-
export const STYLE_HOST_SELECTOR = 'view,symbiote-view,text,symbiote-text,image,symbiote-image,image-background,' +
|
|
56
|
-
'pressable,symbiote-pressable,button,symbiote-button,touchable-opacity,touchable-highlight,' +
|
|
57
|
-
'touchable-native-feedback,touchable-without-feedback,scroll-view,horizontal-scroll-view,' +
|
|
58
|
-
'scroll-content,horizontal-scroll-content,activity-indicator,activity-indicator-spinner,' +
|
|
59
|
-
'safe-area-view,modal,symbiote-modal,sticky-header,input-accessory-view';
|
|
60
|
-
export class SymbioteStyleHost {
|
|
61
|
-
constructor() {
|
|
62
|
-
this.renderer = inject(Renderer2);
|
|
63
|
-
this.host = inject(ElementRef);
|
|
64
|
-
}
|
|
65
|
-
ngOnChanges(changes) {
|
|
66
|
-
for (const name of Object.keys(changes))
|
|
67
|
-
this.renderer.setProperty(this.host.nativeElement, name, changes[name]?.currentValue);
|
|
68
|
-
}
|
|
69
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SymbioteStyleHost, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
70
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.8", type: SymbioteStyleHost, isStandalone: true, selector: "view,symbiote-view,text,symbiote-text,image,symbiote-image,image-background,pressable,symbiote-pressable,button,symbiote-button,touchable-opacity,touchable-highlight,touchable-native-feedback,touchable-without-feedback,scroll-view,horizontal-scroll-view,scroll-content,horizontal-scroll-content,activity-indicator,activity-indicator-spinner,safe-area-view,modal,symbiote-modal,sticky-header,input-accessory-view", inputs: { style: "style", class: "class" }, usesOnChanges: true, ngImport: i0 }); }
|
|
71
|
-
}
|
|
72
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SymbioteStyleHost, decorators: [{
|
|
73
|
-
type: Directive,
|
|
74
|
-
args: [{
|
|
75
|
-
selector: 'view,symbiote-view,text,symbiote-text,image,symbiote-image,image-background,' +
|
|
76
|
-
'pressable,symbiote-pressable,button,symbiote-button,touchable-opacity,touchable-highlight,' +
|
|
77
|
-
'touchable-native-feedback,touchable-without-feedback,scroll-view,horizontal-scroll-view,' +
|
|
78
|
-
'scroll-content,horizontal-scroll-content,activity-indicator,activity-indicator-spinner,' +
|
|
79
|
-
'safe-area-view,modal,symbiote-modal,sticky-header,input-accessory-view',
|
|
80
|
-
// Spelled out rather than referenced: ngtsc statically evaluates decorator metadata, and a
|
|
81
|
-
// computed selector makes it report the class as not standalone — see `callback-host.ts`.
|
|
82
|
-
inputs: ['style', 'class'],
|
|
83
|
-
standalone: true,
|
|
84
|
-
}]
|
|
85
|
-
}] });
|
|
86
|
-
//# sourceMappingURL=style-host.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"style-host.js","sourceRoot":"","sources":["../../src/style-host.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,EAAE;AACF,gGAAgG;AAChG,oGAAoG;AACpG,mGAAmG;AACnG,iGAAiG;AACjG,4EAA4E;AAC5E,EAAE;AACF,qGAAqG;AACrG,oGAAoG;AACpG,kGAAkG;AAClG,2DAA2D;AAC3D,EAAE;AACF,mGAAmG;AACnG,mGAAmG;AACnG,mDAAmD;AACnD,EAAE;AACF,oGAAoG;AACpG,8FAA8F;AAC9F,EAAE;AACF,sGAAsG;AACtG,kGAAkG;AAClG,iGAAiG;AACjG,mGAAmG;AACnG,oGAAoG;AACpG,iGAAiG;AACjG,iGAAiG;AACjG,gFAAgF;AAChF,EAAE;AACF,qGAAqG;AACrG,qGAAqG;AACrG,qGAAqG;AACrG,EAAE;AACF,sGAAsG;AACtG,mGAAmG;AACnG,qGAAqG;AACrG,mGAAmG;AACnG,kGAAkG;AAClG,4FAA4F;AAC5F,EAAE;AACF,kGAAkG;AAClG,iGAAiG;AACjG,gDAAgD;AAChD,EAAE;AACF,mGAAmG;AACnG,0DAA0D;AAC1D,EAAE;AACF,2FAA2F;AAC3F,gGAAgG;AAChG,mGAAmG;AACnG,8EAA8E;AAE9E,OAAO,EACL,SAAS,EACT,UAAU,EACV,SAAS,EAGT,MAAM,GACP,MAAM,eAAe,CAAC;;AAEvB,sGAAsG;AACtG,MAAM,CAAC,MAAM,mBAAmB,GAC9B,8EAA8E;IAC9E,4FAA4F;IAC5F,0FAA0F;IAC1F,yFAAyF;IACzF,wEAAwE,CAAC;AAc3E,MAAM,OAAO,iBAAiB;IAZ9B;QAamB,aAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;QAC7B,SAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;KAU5C;IARC,WAAW,CAAC,OAAsB;QAChC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;YACrC,IAAI,CAAC,QAAQ,CAAC,WAAW,CACvB,IAAI,CAAC,IAAI,CAAC,aAAa,EACvB,IAAI,EACJ,OAAO,CAAC,IAAI,CAAC,EAAE,YAAY,CAC5B,CAAC;IACN,CAAC;8GAXU,iBAAiB;kGAAjB,iBAAiB;;2FAAjB,iBAAiB;kBAZ7B,SAAS;mBAAC;oBACT,QAAQ,EACN,8EAA8E;wBAC9E,4FAA4F;wBAC5F,0FAA0F;wBAC1F,yFAAyF;wBACzF,wEAAwE;oBAC1E,2FAA2F;oBAC3F,0FAA0F;oBAC1F,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;oBAC1B,UAAU,EAAE,IAAI;iBACjB"}
|
package/src/style-host.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
// The directive that claims `[style]` and `[class]` on the elements that bind them.
|
|
2
|
-
//
|
|
3
|
-
// IT IS A CORRECTNESS REQUIREMENT BEFORE IT IS AN OPTIMISATION, which is what separates it from
|
|
4
|
-
// `callback-host.ts`. An RN `StyleProp` is allowed to be an ARRAY, and Angular's own styling engine
|
|
5
|
-
// cannot represent one: `ɵɵstyleMap` decomposes the value key by key, so `applyStyling` takes each
|
|
6
|
-
// array MEMBER as a style key and throws inside change detection. Device-diagnosed 2026-09-02 on
|
|
7
|
-
// ImageBackground, and pinned ever since by `renderer/style-input.test.ts`.
|
|
8
|
-
//
|
|
9
|
-
// A declared input is what keeps the value away from that engine — `checkStylingMap` hands the whole
|
|
10
|
-
// value to the input instead of walking it (`render3/instructions/styling.ts:259-288`). So when the
|
|
11
|
-
// tag directives were withheld from runtime matching, the claim had to come from somewhere, or an
|
|
12
|
-
// array style would throw on every screen that writes one.
|
|
13
|
-
//
|
|
14
|
-
// AND IT IS ALSO THE FASTER PATH, which is why `class` rides the same selector. Unclaimed, a style
|
|
15
|
-
// object reaches the renderer once per KEY — measured at ~7 000 renderer calls on the thousand-row
|
|
16
|
-
// bench row against 4 000 when an input claims it.
|
|
17
|
-
//
|
|
18
|
-
// MATCHED ON THE TAG, and the first spelling of this directive matched on `[style],[class]` instead
|
|
19
|
-
// — which does not work, for a reason worth keeping because it is invisible from the outside.
|
|
20
|
-
//
|
|
21
|
-
// `[style]` IS NOT A PROPERTY BINDING. The compiler routes it to `ɵɵstyleMap`, a styling instruction,
|
|
22
|
-
// and the name never lands in the `AttributeMarker.Bindings` run that `findAttrIndexInNode` walks
|
|
23
|
-
// (upstream `node_selector_matcher.ts:240-282`, where `Classes` and `Styles` markers are SKIPPED
|
|
24
|
-
// outright). So a directive selected on `[style]` never matches an element that binds one, and the
|
|
25
|
-
// value goes to the styling engine exactly as if no directive existed. Measured: the AOT fixture in
|
|
26
|
-
// `style-input-aot.test.ts` imports `SYMBIOTE_ELEMENTS`, carries this directive, and still threw
|
|
27
|
-
// `Unsupported styling type: function`. `[onPress]` and every other `on*` name DO work that way,
|
|
28
|
-
// which is why `callback-host.ts` can be attribute-matched and this one cannot.
|
|
29
|
-
//
|
|
30
|
-
// SO IT IS A TAG DIRECTIVE, and it is instantiated per element like the ones it replaces — including
|
|
31
|
-
// on elements that bind no style at all, because a tag selector is the only one that can match. What
|
|
32
|
-
// is bought is its SHAPE: two inputs and two injections against the withheld classes' 279 and three.
|
|
33
|
-
//
|
|
34
|
-
// MEASURED, AND SMALLER THAN THE IDEA PROMISED. On the JavaScriptCore ladder, withholding a directive
|
|
35
|
-
// outright is worth ~70-91 ms over ten thousand elements (`WITHHOLDING it`, 7.0-9.1 us each) — but
|
|
36
|
-
// the adapter cannot take that, because this directive has to stay. What it reads against a bare tag
|
|
37
|
-
// AFTER the change is 58.2 / 58.8 / 71.0 ms, against `a directive at all` at 50.2 / 55.9 / 68.3 in
|
|
38
|
-
// the same runs. Those two are the same number: a directive costs what a directive costs, thin or
|
|
39
|
-
// fat, and nearly all of it is matching and instantiating rather than inputs or injections.
|
|
40
|
-
//
|
|
41
|
-
// So the change is worth the FAT-TO-THIN difference and not the whole crossing — the ladder's own
|
|
42
|
-
// `279 inputs, not 1` (~19.9 ms) plus `the ChangeDetectorRef` (~10-31 ms). Recorded at that size
|
|
43
|
-
// rather than at the one the plan was aimed at.
|
|
44
|
-
//
|
|
45
|
-
// NO `ChangeDetectorRef` HERE. A style write is an ordinary prop write and marks nothing; the view
|
|
46
|
-
// marking belongs to the callback path and lives with it.
|
|
47
|
-
//
|
|
48
|
-
// THE FOUR TAGS THAT KEEP THEIR OWN DIRECTIVE ARE ABSENT from the selector — `text-input`,
|
|
49
|
-
// `text-input-multiline`, `switch` and `refresh-control`. Their fat directive still matches and
|
|
50
|
-
// already declares `style`, and two directives claiming one input both receive it and both forward
|
|
51
|
-
// it, which is the `unchanged` double write this adapter has measured before.
|
|
52
|
-
|
|
53
|
-
import {
|
|
54
|
-
Directive,
|
|
55
|
-
ElementRef,
|
|
56
|
-
Renderer2,
|
|
57
|
-
type OnChanges,
|
|
58
|
-
type SimpleChanges,
|
|
59
|
-
inject,
|
|
60
|
-
} from '@angular/core';
|
|
61
|
-
|
|
62
|
-
/** Exported for the guard, which derives the same set from the withheld directives' own selectors. */
|
|
63
|
-
export const STYLE_HOST_SELECTOR =
|
|
64
|
-
'view,symbiote-view,text,symbiote-text,image,symbiote-image,image-background,' +
|
|
65
|
-
'pressable,symbiote-pressable,button,symbiote-button,touchable-opacity,touchable-highlight,' +
|
|
66
|
-
'touchable-native-feedback,touchable-without-feedback,scroll-view,horizontal-scroll-view,' +
|
|
67
|
-
'scroll-content,horizontal-scroll-content,activity-indicator,activity-indicator-spinner,' +
|
|
68
|
-
'safe-area-view,modal,symbiote-modal,sticky-header,input-accessory-view';
|
|
69
|
-
|
|
70
|
-
@Directive({
|
|
71
|
-
selector:
|
|
72
|
-
'view,symbiote-view,text,symbiote-text,image,symbiote-image,image-background,' +
|
|
73
|
-
'pressable,symbiote-pressable,button,symbiote-button,touchable-opacity,touchable-highlight,' +
|
|
74
|
-
'touchable-native-feedback,touchable-without-feedback,scroll-view,horizontal-scroll-view,' +
|
|
75
|
-
'scroll-content,horizontal-scroll-content,activity-indicator,activity-indicator-spinner,' +
|
|
76
|
-
'safe-area-view,modal,symbiote-modal,sticky-header,input-accessory-view',
|
|
77
|
-
// Spelled out rather than referenced: ngtsc statically evaluates decorator metadata, and a
|
|
78
|
-
// computed selector makes it report the class as not standalone — see `callback-host.ts`.
|
|
79
|
-
inputs: ['style', 'class'],
|
|
80
|
-
standalone: true,
|
|
81
|
-
})
|
|
82
|
-
export class SymbioteStyleHost implements OnChanges {
|
|
83
|
-
private readonly renderer = inject(Renderer2);
|
|
84
|
-
private readonly host = inject(ElementRef);
|
|
85
|
-
|
|
86
|
-
ngOnChanges(changes: SimpleChanges): void {
|
|
87
|
-
for (const name of Object.keys(changes))
|
|
88
|
-
this.renderer.setProperty(
|
|
89
|
-
this.host.nativeElement,
|
|
90
|
-
name,
|
|
91
|
-
changes[name]?.currentValue,
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
}
|