@symbiote-native/engine 0.2.0 → 0.4.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.
Files changed (75) hide show
  1. package/README.md +24 -11
  2. package/build/accessibility-props.d.ts +19 -0
  3. package/build/accessibility-props.js +209 -0
  4. package/build/action-sheet-ios/index.js +4 -1
  5. package/build/animated/animations/composition.js +3 -1
  6. package/build/animated/animations/spring-config.js +4 -1
  7. package/build/animated/animations/spring.js +23 -9
  8. package/build/animated/animations/timing.js +3 -2
  9. package/build/animated/bezier.js +2 -1
  10. package/build/animated/color.js +4 -2
  11. package/build/animated/event.js +7 -3
  12. package/build/animated/graph.js +8 -3
  13. package/build/animated/index.d.ts +2 -2
  14. package/build/animated/index.js +2 -2
  15. package/build/animated/interpolation.js +7 -2
  16. package/build/animated/mock.js +1 -1
  17. package/build/animated/native/native-animated.js +1 -1
  18. package/build/animated/operators.js +27 -6
  19. package/build/animated/props.js +1 -1
  20. package/build/animated/rgba.js +18 -3
  21. package/build/animated/style.js +13 -3
  22. package/build/animated/value-xy.d.ts +8 -5
  23. package/build/animated/value-xy.js +4 -1
  24. package/build/app-registry/index.d.ts +1 -0
  25. package/build/app-registry/index.js +15 -5
  26. package/build/appearance/index.js +4 -1
  27. package/build/commit.d.ts +35 -0
  28. package/build/commit.js +613 -74
  29. package/build/debug.js +10 -4
  30. package/build/events/index.js +142 -88
  31. package/build/fabric-props.js +171 -11
  32. package/build/fabric.d.ts +10 -3
  33. package/build/fabric.js +11 -2
  34. package/build/host-behavior.d.ts +47 -0
  35. package/build/host-behavior.js +262 -0
  36. package/build/host-instance/index.d.ts +2 -10
  37. package/build/host-instance/index.js +13 -46
  38. package/build/image-loader.js +3 -2
  39. package/build/index.d.ts +24 -17
  40. package/build/index.js +27 -13
  41. package/build/interaction-manager/index.js +2 -1
  42. package/build/layout-animation/index.js +12 -4
  43. package/build/native-events.js +2 -1
  44. package/build/native-modules/index.js +3 -1
  45. package/build/node.d.ts +121 -2
  46. package/build/node.js +623 -41
  47. package/build/pan-responder/index.js +20 -9
  48. package/build/permissions-android/index.js +3 -1
  49. package/build/platform/index.android.d.ts +1 -1
  50. package/build/platform/index.android.js +1 -1
  51. package/build/platform/index.ios.d.ts +1 -1
  52. package/build/platform-color/index.js +3 -1
  53. package/build/post-commit.d.ts +1 -0
  54. package/build/post-commit.js +6 -0
  55. package/build/process-background-image/index.js +30 -9
  56. package/build/process-filter.js +11 -4
  57. package/build/registry.js +10 -2
  58. package/build/report-error.js +4 -1
  59. package/build/share/index.android.js +1 -1
  60. package/build/share/index.ios.js +1 -1
  61. package/build/status-bar/index.android.js +3 -4
  62. package/build/status-bar/index.ios.js +1 -1
  63. package/build/style-registry/index.d.ts +11 -2
  64. package/build/style-registry/index.js +223 -169
  65. package/build/style-registry/scope.d.ts +15 -1
  66. package/build/style-registry/scope.js +29 -27
  67. package/build/styles.d.ts +11 -1
  68. package/build/surface.d.ts +1 -1
  69. package/build/surface.js +8 -0
  70. package/build/tags.d.ts +1 -0
  71. package/build/tags.js +31 -1
  72. package/build/touch-history.js +9 -2
  73. package/build/vibration/index.ios.js +1 -1
  74. package/build/view-config.js +20 -2
  75. package/package.json +12 -2
@@ -42,7 +42,7 @@ const ANIMATION_PROPERTY = {
42
42
  // (`FabricUIManager?.configureNextLayoutAnimation` in LayoutAnimation.js) before
43
43
  // calling it, no `as` cast needed.
44
44
  function hasConfigureNextLayoutAnimation(value) {
45
- return isRecord(value) && typeof value.configureNextLayoutAnimation === 'function';
45
+ return (isRecord(value) && typeof value.configureNextLayoutAnimation === 'function');
46
46
  }
47
47
  // Resolved FRESH on every configureNext, deliberately NOT memoized. Either mechanism can
48
48
  // come and go at runtime (the Fabric global installs during bootstrap; a TurboModule can be
@@ -93,9 +93,15 @@ const Presets = {
93
93
  linear: createLayoutAnimation(PRESET_DURATION.linear, ANIMATION_TYPE.linear, ANIMATION_PROPERTY.opacity),
94
94
  spring: {
95
95
  duration: PRESET_DURATION.spring,
96
- create: { type: ANIMATION_TYPE.linear, property: ANIMATION_PROPERTY.opacity },
96
+ create: {
97
+ type: ANIMATION_TYPE.linear,
98
+ property: ANIMATION_PROPERTY.opacity,
99
+ },
97
100
  update: { type: ANIMATION_TYPE.spring, springDamping: SPRING_DAMPING },
98
- delete: { type: ANIMATION_TYPE.linear, property: ANIMATION_PROPERTY.opacity },
101
+ delete: {
102
+ type: ANIMATION_TYPE.linear,
103
+ property: ANIMATION_PROPERTY.opacity,
104
+ },
99
105
  },
100
106
  };
101
107
  // ---- enabled gate --------------------------------------------------------
@@ -154,7 +160,9 @@ function configureNext(config, onAnimationDidEnd, onAnimationDidFail) {
154
160
  class LayoutAnimationImpl {
155
161
  // Frozen so callers can't mutate the shared type/property tables.
156
162
  Types = Object.freeze({ ...ANIMATION_TYPE });
157
- Properties = Object.freeze({ ...ANIMATION_PROPERTY });
163
+ Properties = Object.freeze({
164
+ ...ANIMATION_PROPERTY,
165
+ });
158
166
  Presets = Presets;
159
167
  // Methods (not class fields) so they stay overridable under
160
168
  // useDefineForClassFields.
@@ -70,7 +70,8 @@ export function setDeviceEventSource(source) {
70
70
  // would throw "undefined is not a function" — the `?.` guards the module, not a
71
71
  // missing method. Mirrors RN's NativeEventEmitter constructor probe.
72
72
  function hasObserveCounters(module) {
73
- return typeof module.addListener === 'function' && typeof module.removeListeners === 'function';
73
+ return (typeof module.addListener === 'function' &&
74
+ typeof module.removeListeners === 'function');
74
75
  }
75
76
  // Subscribe to events for one native module. Mirrors RN's NativeEventEmitter: each
76
77
  // `addListener` also pings the module's `addListener` counter, and removal pings
@@ -110,7 +110,9 @@ export function createDeviceEventModule(config) {
110
110
  installDeviceEventHub();
111
111
  const resolved = getModule();
112
112
  const bindModule = config.bindModuleToEmitter ?? true;
113
- const boundModule = bindModule && resolved !== null && hasEventEmitterShape(resolved) ? resolved : undefined;
113
+ const boundModule = bindModule && resolved !== null && hasEventEmitterShape(resolved)
114
+ ? resolved
115
+ : undefined;
114
116
  emitter = new NativeEventEmitter(boundModule);
115
117
  config.onEmitterCreated?.(emitter, resolved);
116
118
  }
package/build/node.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import type { IFabricNode, IFabricProps, IRootTag, IMeasureOnSuccess, IMeasureInWindowOnSuccess, IMeasureLayoutOnSuccess } from './fabric';
2
+ import { type IClassNameValue } from './style-registry';
3
+ import { type IPayloadFold } from './host-behavior';
1
4
  declare const BRAND: unique symbol;
2
5
  export declare const RAW_TEXT_COMPONENT = "RCTRawText";
3
6
  export declare const TEXT_COMPONENT = "RCTText";
@@ -13,26 +16,142 @@ export type IListener = (event: ISymbioteEvent) => unknown;
13
16
  export declare function isSymbioteEvent(value: unknown): value is ISymbioteEvent;
14
17
  export interface ISymbioteNode {
15
18
  readonly [BRAND]: true;
16
- readonly component: string;
19
+ component: string;
17
20
  readonly isText: boolean;
18
21
  props: Record<string, unknown>;
19
22
  listeners: Map<string, IListener> | undefined;
20
23
  children: ISymbioteNode[];
21
24
  parent: ISymbioteNode | undefined;
25
+ dirty: boolean;
26
+ propsDirty: boolean;
27
+ hasAriaAlias: boolean;
28
+ payloadFold: IPayloadFold | undefined;
29
+ structureDirty: boolean;
30
+ committed: IMirror | undefined;
31
+ styleParts: IClassStyleParts | undefined;
32
+ measure(callback: IMeasureOnSuccess): void;
33
+ measureInWindow(callback: IMeasureInWindowOnSuccess): void;
34
+ measureLayout(relativeToNativeNode: ISymbioteNode | number, onSuccess: IMeasureLayoutOnSuccess, onFail?: () => void): void;
35
+ setNativeProps(nativeProps: Record<string, unknown>): void;
36
+ focus(): void;
37
+ blur(): void;
22
38
  }
23
- export declare function createElement(component: string, isText?: boolean): ISymbioteNode;
39
+ export interface IMirror {
40
+ handle: IFabricNode;
41
+ tag: number;
42
+ rootTag: IRootTag;
43
+ props: IFabricProps;
44
+ children: readonly ISymbioteNode[];
45
+ viewName: string;
46
+ parent: ISymbioteNode | undefined;
47
+ owner: ISymbioteNode;
48
+ }
49
+ /**
50
+ * The committed record for `node`, or `undefined` if it has never been committed - or if `node` is
51
+ * not the raw retained node at all.
52
+ *
53
+ * That second case is the reason this is a function rather than a bare `node.committed` read. The
54
+ * engine identifies a node BY IDENTITY, and the classic way to break that is to hand the engine a
55
+ * wrapper instead of the node: a Vue `reactive()`/deep-`ref()` Proxy around a host element is the
56
+ * one that actually happens (see the vue-adapter-reactivity skill; `shallowRef` is the fix).
57
+ *
58
+ * The old WeakMap caught this for free - a Proxy is a different object, so `mirror.get(proxy)` missed
59
+ * and every imperative API bailed with a clear "node not committed". A plain property read does NOT:
60
+ * a Proxy forwards `proxy.committed` straight to the target and hands back a real record, whose
61
+ * `handle` Vue would then deep-wrap on the way out. That handle is a JSI host object; a Proxy around
62
+ * it reaches `cloneNodeWithNewProps` and fails somewhere deep in native, far from the cause.
63
+ *
64
+ * So the identity check that was implicit in the WeakMap is explicit here: a record written on the
65
+ * raw node names it, and `record.owner !== node` means whatever we were handed is not that node.
66
+ * One reference comparison, and the wrap now fails LOUDER than it used to rather than quieter.
67
+ */
68
+ export declare function committedOf(node: ISymbioteNode): IMirror | undefined;
69
+ export declare function createElement(component: string, isText?: boolean, tag?: string): ISymbioteNode;
24
70
  export declare function createRawText(text: string): ISymbioteNode;
25
71
  export declare function isSymbioteNode(value: unknown): value is ISymbioteNode;
26
72
  export declare function debugNodeId(node: ISymbioteNode): number;
27
73
  export declare const ANCHOR_COMPONENT = "#anchor";
28
74
  export declare function createAnchor(): ISymbioteNode;
29
75
  export declare function isAnchor(node: ISymbioteNode): boolean;
76
+ export declare function isEmptyRawText(node: ISymbioteNode): boolean;
77
+ /**
78
+ * Change which Fabric view a node commits as, keeping the node's identity.
79
+ *
80
+ * The commit walk already re-creates a node whose `viewName` no longer matches its committed one —
81
+ * that is how a `<Text>` moving in or out of another `<Text>` flips between RCTText and
82
+ * RCTVirtualText (`commit.ts`, reason `view-kind`). This exposes the same door for a prop-driven
83
+ * view choice, so `intrinsicWhen` is honoured on UPDATE and not only at create.
84
+ *
85
+ * The POLICY stays out of the engine: which prop decides, and which view it decides between, lives
86
+ * in `HOST_PRIMITIVES` and is read by `resolveIntrinsicTag` in `@symbiote-native/components`. The
87
+ * engine only knows how to swap the name — the same split every other spec-driven fold has here.
88
+ *
89
+ * A no-op when the name is unchanged, so a renderer may call it on every update without comparing
90
+ * first.
91
+ */
92
+ export declare function setNodeComponent(node: ISymbioteNode, component: string): void;
93
+ export declare function markDirty(node: ISymbioteNode): void;
94
+ export declare function markPropsDirty(node: ISymbioteNode): void;
95
+ export declare function markStructureDirty(parent: ISymbioteNode): void;
96
+ export declare function takePropStats(): {
97
+ writes: number;
98
+ noops: number;
99
+ };
30
100
  export declare function setProp(node: ISymbioteNode, key: string, value: unknown): void;
101
+ /**
102
+ * Install a listener the BEHAVIOR owns, bypassing the ownership check.
103
+ *
104
+ * `setEventListener` diverts an owned name into the stash, which is right for an app listener and
105
+ * circular for the behavior's own dispatcher — it would stash itself and never occupy the slot it
106
+ * exists to hold. This is the one writer allowed past that gate.
107
+ */
108
+ export declare function setBehaviorListener(node: ISymbioteNode, name: string, listener: IListener): void;
31
109
  export declare function setEventListener(node: ISymbioteNode, name: string, value: unknown): void;
110
+ export interface IClassStyleParts {
111
+ classStyle: unknown;
112
+ explicitStyle: unknown;
113
+ hiddenStyle: unknown;
114
+ className: IClassNameValue | undefined;
115
+ isPressed: boolean;
116
+ activeStyle: unknown;
117
+ activeStyleFromCallback: boolean;
118
+ }
119
+ /**
120
+ * Stop a node painting without unmounting it, or let it paint again.
121
+ *
122
+ * The seam React's `Activity`/`Suspense` reach for through `hideInstance`/`unhideInstance`. It
123
+ * lives in the engine rather than an adapter because the reversibility problem — restoring the
124
+ * author's style byte for byte — belongs to whoever owns the style merge, and that is here.
125
+ */
126
+ export declare function setNodeHidden(node: ISymbioteNode, hidden: boolean): void;
127
+ /**
128
+ * Put a node into (or out of) its pressed state, so `.x:active` rules apply.
129
+ *
130
+ * The engine-owned half of what `:active` is on the web: the press state resolves BELOW the
131
+ * framework and never crosses into it, which is what lets a pressable be an intrinsic tag rather
132
+ * than a component (`.claude/rules/host-primitive-tier.md`, tier 2). A component is forced only
133
+ * when the TEMPLATE must read the state — `v-slot="{ pressed }"` and the function form of `style`
134
+ * — and this exists so the common case does not have to.
135
+ *
136
+ * Costs nothing when no `:active` rule is registered anywhere: `resolveActiveClassName` hands back
137
+ * the very same object the unpressed path returns, so `isAlreadyPublished` turns the re-push away
138
+ * and the node is never dirtied.
139
+ */
140
+ export declare function setNodePressed(node: ISymbioteNode, pressed: boolean): void;
32
141
  export declare function getExplicitStyle(node: ISymbioteNode): unknown;
33
142
  export declare function routeProp(node: ISymbioteNode, key: string, value: unknown): void;
34
143
  export declare function setText(node: ISymbioteNode, text: string): void;
35
144
  export declare function appendChild(parent: ISymbioteNode, child: ISymbioteNode): void;
36
145
  export declare function insertBefore(parent: ISymbioteNode, child: ISymbioteNode, beforeChild: ISymbioteNode): void;
37
146
  export declare function removeChild(parent: ISymbioteNode, child: ISymbioteNode): void;
147
+ export interface ITreeCensus {
148
+ nodes: number;
149
+ anchors: number;
150
+ emptyRawTexts: number;
151
+ /** Nodes the commit walk actually reconciles: `nodes` minus everything it skips. */
152
+ renderable: number;
153
+ /** children.length of every parent holding at least one skipped child, widest first. */
154
+ flattenWidths: number[];
155
+ }
156
+ export declare function censusRetainedTree(roots: readonly ISymbioteNode[]): ITreeCensus;
38
157
  export {};