@symbiote-native/angular 2.0.1 → 3.0.1
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 +5 -4
- package/babel-register-composed.cjs +0 -8
- package/build/angular/callback-host.d.ts +25 -0
- package/build/angular/callback-host.js +211 -0
- package/build/angular/callback-host.js.map +1 -0
- package/build/angular/change-detection-flush.d.ts +14 -2
- package/build/angular/change-detection-flush.js +54 -6
- package/build/angular/change-detection-flush.js.map +1 -1
- package/build/angular/components/flat-list/index.js +39 -86
- package/build/angular/components/flat-list/index.js.map +1 -1
- package/build/angular/components/image-shared.js +49 -110
- package/build/angular/components/image-shared.js.map +1 -1
- package/build/angular/components/keyboard-avoiding-view/index.js +33 -72
- package/build/angular/components/keyboard-avoiding-view/index.js.map +1 -1
- package/build/angular/components/modal/index.js +58 -103
- package/build/angular/components/modal/index.js.map +1 -1
- package/build/angular/components/pressable-props.d.ts +2 -0
- package/build/angular/components/section-list/index.js +20 -103
- package/build/angular/components/section-list/index.js.map +1 -1
- package/build/angular/components/touchable-props.d.ts +1 -0
- package/build/angular/components/virtualized-list/directives.js +32 -22
- package/build/angular/components/virtualized-list/directives.js.map +1 -1
- package/build/angular/components/virtualized-list/index.d.ts +7 -1
- package/build/angular/components/virtualized-list/index.js +350 -359
- package/build/angular/components/virtualized-list/index.js.map +1 -1
- package/build/angular/components/virtualized-section-list/directives.js +20 -12
- package/build/angular/components/virtualized-section-list/directives.js.map +1 -1
- package/build/angular/components/virtualized-section-list/index.js +36 -142
- package/build/angular/components/virtualized-section-list/index.js.map +1 -1
- package/build/angular/components.d.ts +1 -1
- package/build/angular/components.js +1 -1
- package/build/angular/components.js.map +1 -1
- package/build/angular/create-portal/index.js +9 -8
- package/build/angular/create-portal/index.js.map +1 -1
- package/build/angular/create-tunnel/index.js +13 -12
- package/build/angular/create-tunnel/index.js.map +1 -1
- package/build/angular/descriptor-to-angular/index.js +6 -6
- package/build/angular/descriptor-to-angular/index.js.map +1 -1
- package/build/angular/diagnostics.d.ts +13 -0
- package/build/angular/diagnostics.js +18 -0
- package/build/angular/diagnostics.js.map +1 -1
- package/build/angular/element-props.d.ts +1 -3
- package/build/angular/elements.d.ts +46 -32
- package/build/angular/elements.js +217 -434
- package/build/angular/elements.js.map +1 -1
- package/build/angular/index.d.ts +3 -1
- package/build/angular/index.js +44 -13
- package/build/angular/index.js.map +1 -1
- package/build/angular/modules/animated/animated-leaf-binder.js +0 -2
- package/build/angular/modules/animated/animated-leaf-binder.js.map +1 -1
- package/build/angular/modules/animated/create-animated-component.js +69 -74
- package/build/angular/modules/animated/create-animated-component.js.map +1 -1
- package/build/angular/modules/animated/index.d.ts +30 -19
- package/build/angular/modules/status-bar/index.js +2 -8
- package/build/angular/modules/status-bar/index.js.map +1 -1
- package/build/angular/primitives/index.d.ts +14 -28
- package/build/angular/primitives/index.js +44 -107
- package/build/angular/primitives/index.js.map +1 -1
- package/build/angular/primitives/shared.d.ts +1 -0
- package/build/angular/primitives/shared.js +47 -32
- package/build/angular/primitives/shared.js.map +1 -1
- package/build/angular/render/index.js +40 -3
- package/build/angular/render/index.js.map +1 -1
- package/build/angular/renderer/index.d.ts +80 -0
- package/build/angular/renderer/index.js +324 -114
- package/build/angular/renderer/index.js.map +1 -1
- package/build/angular/runtime-matching.d.ts +9 -0
- package/build/angular/runtime-matching.js +82 -0
- package/build/angular/runtime-matching.js.map +1 -0
- package/build/angular/services/color-scheme.service.js +4 -4
- package/build/angular/services/color-scheme.service.js.map +1 -1
- package/build/angular/services/window-dimensions.service.js +4 -4
- package/build/angular/services/window-dimensions.service.js.map +1 -1
- package/build/angular/style-host.d.ts +11 -0
- package/build/angular/style-host.js +86 -0
- package/build/angular/style-host.js.map +1 -0
- package/package.json +6 -6
- package/src/callback-host.ts +229 -0
- package/src/change-detection-flush.ts +62 -9
- package/src/components/flat-list/index.ts +2 -1
- package/src/components/keyboard-avoiding-view/index.ts +8 -0
- package/src/components/pressable-props.ts +8 -0
- package/src/components/touchable-props.ts +4 -0
- package/src/components/virtualized-list/index.ts +98 -45
- package/src/components.ts +0 -2
- package/src/diagnostics.ts +18 -0
- package/src/element-props.ts +1 -5
- package/src/elements.ts +159 -38
- package/src/index.ts +47 -14
- package/src/primitives/index.ts +14 -70
- package/src/primitives/shared.ts +21 -6
- package/src/render/index.ts +56 -2
- package/src/renderer/index.ts +378 -104
- package/src/runtime-matching.ts +80 -0
- package/src/style-host.ts +94 -0
- package/tsconfig.angular.base.json +11 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Keeping a directive for the TEMPLATE CHECKER and taking it out of the runtime.
|
|
2
|
+
//
|
|
3
|
+
// An element directive here exists so `<view [testID]="42">` is a compile error and `<view
|
|
4
|
+
// [testID]="'row'">` is not. That is ngtsc's job, done over the TypeScript source before anything
|
|
5
|
+
// runs. What it costs at RUN time is an instance per element: Angular matches the directive, builds
|
|
6
|
+
// it, injects into it, and calls `ngOnChanges`, which forwards each changed input with
|
|
7
|
+
// `renderer.setProperty(host, name, value)` — the same call `ɵɵproperty` makes DIRECTLY on an element
|
|
8
|
+
// no directive claimed. Same destination, one hop shorter.
|
|
9
|
+
//
|
|
10
|
+
// Measured on JavaScriptCore at ~8.5-9.4 us per element (`core/engine/cpp/tests/js/
|
|
11
|
+
// angular-directive-cost.itest.ts`, the `a directive at all` row), and between the two Angular bench
|
|
12
|
+
// arms at ~86 ms on a thousand-row screen — the largest single cost this adapter still carries.
|
|
13
|
+
//
|
|
14
|
+
// HOW: `findDirectiveDefMatches` walks `tView.directiveRegistry` and asks `isNodeMatchingSelectorList`
|
|
15
|
+
// with `def.selectors` (upstream `render3/instructions/shared.ts:466-501`). A def whose selector list
|
|
16
|
+
// is EMPTY is registered, iterated, and never matched — while its class, its decorator and its
|
|
17
|
+
// declared inputs are untouched, which is all the template checker reads. So this is one mutation per
|
|
18
|
+
// class at module load rather than a build step: no linker output to rewrite, and it behaves the same
|
|
19
|
+
// under JIT as under the partial-compiled path a device runs.
|
|
20
|
+
//
|
|
21
|
+
// WHAT A WITHHELD DIRECTIVE STOPS DOING, stated in full because none of it fails a type check:
|
|
22
|
+
//
|
|
23
|
+
// the forward `ngOnChanges` no longer runs. `ɵɵproperty` writes the same name and value
|
|
24
|
+
// straight to the renderer, so the engine sees the identical prop — asserted by
|
|
25
|
+
// census in the ladder (`unmatched` reads created=10002 setProps=10002).
|
|
26
|
+
// the `on*` wrap an `on*` PROP is called by the ENGINE, so Angular learns nothing unless
|
|
27
|
+
// something marks the view. That moved to `SymbioteRenderer.setProperty`, which
|
|
28
|
+
// every path reaches whether a directive claimed the binding or not.
|
|
29
|
+
// the style shadow `[style]` and `[class]` are no longer claimed by an input, so `ɵɵstyleMap` and
|
|
30
|
+
// `ɵɵclassMap` resolve them per KEY again — ~7 000 renderer calls on the bench
|
|
31
|
+
// row where the shadow made 4 000. That is a REGRESSION and it is accepted: the
|
|
32
|
+
// bare bench arm already pays it and is still ~86 ms ahead of the matched one.
|
|
33
|
+
//
|
|
34
|
+
// WHICH IS WHY THE LIST IS NOT "ALL OF THEM". A directive with real runtime behaviour must go on
|
|
35
|
+
// matching, and here that is the `ValueChangeElement` chain (`text-input`, `switch` — they listen for
|
|
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.
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Take these directives out of Angular's runtime matcher, leaving ngtsc's view of them untouched.
|
|
42
|
+
*
|
|
43
|
+
* Idempotent, and silent on anything that is not a compiled directive — a class whose `ɵdir` cannot
|
|
44
|
+
* be read is left alone rather than reported, because the only way to reach that state is a build
|
|
45
|
+
* that never compiled it, which fails far louder elsewhere.
|
|
46
|
+
*/
|
|
47
|
+
export function withholdFromRuntimeMatching(types: readonly unknown[]): void {
|
|
48
|
+
for (const type of types) {
|
|
49
|
+
if (typeof type !== 'function') continue;
|
|
50
|
+
const definition: unknown = Reflect.get(type, 'ɵdir');
|
|
51
|
+
if (typeof definition !== 'object' || definition === null) continue;
|
|
52
|
+
const selectors: unknown = Reflect.get(definition, 'selectors');
|
|
53
|
+
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
|
+
// Emptied in place rather than replaced: the def holds this array, and reading `ɵdir` under JIT
|
|
63
|
+
// is what compiles the directive, so the object in hand is the one the matcher will walk.
|
|
64
|
+
selectors.length = 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
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
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
}
|
|
@@ -9,6 +9,17 @@
|
|
|
9
9
|
"compilerOptions": {
|
|
10
10
|
"target": "ES2022",
|
|
11
11
|
"module": "ES2022",
|
|
12
|
+
// ANGULAR'S OWN SETTING, and it defaults the other way at this target. Every `@Input()` on a
|
|
13
|
+
// directive is a bare field DECLARATION, so with ES2022 class-field semantics each one is a real
|
|
14
|
+
// `[[Define]]` on every instance — `SymbioteElement` declares 279 of them, and a screen written
|
|
15
|
+
// against `SYMBIOTE_ELEMENTS` instantiates a directive per TAG. Angular's own integration
|
|
16
|
+
// configs carry this line beside the same target (`.vendors/angular/integration/*/tsconfig.json`),
|
|
17
|
+
// and a declaration with no initializer emits nothing at all under it.
|
|
18
|
+
//
|
|
19
|
+
// It is a CORRECTNESS setting there before it is a speed one: a subclass field declaration
|
|
20
|
+
// re-defines an inherited property to `undefined` under define semantics, which is how an input
|
|
21
|
+
// set before construction finishes gets silently erased.
|
|
22
|
+
"useDefineForClassFields": false,
|
|
12
23
|
"moduleResolution": "Bundler",
|
|
13
24
|
"strict": true,
|
|
14
25
|
"skipLibCheck": true,
|