@ng-native/fabric 0.0.1 → 0.1.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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +54 -1
  3. package/dist/color-mix.d.ts +20 -0
  4. package/dist/color-mix.js +306 -0
  5. package/dist/color-mix.js.map +1 -0
  6. package/dist/css.d.ts +615 -0
  7. package/dist/css.js +1405 -0
  8. package/dist/css.js.map +1 -0
  9. package/dist/defer-triggers.d.ts +53 -0
  10. package/dist/defer-triggers.js +184 -0
  11. package/dist/defer-triggers.js.map +1 -0
  12. package/dist/engine.d.ts +1066 -0
  13. package/dist/engine.js +2764 -0
  14. package/dist/engine.js.map +1 -0
  15. package/dist/fabric.d.ts +20 -0
  16. package/dist/fabric.js +44 -0
  17. package/dist/fabric.js.map +1 -0
  18. package/dist/forms-inputs.d.ts +15 -0
  19. package/dist/forms-inputs.js +54 -0
  20. package/dist/forms-inputs.js.map +1 -0
  21. package/dist/host.d.ts +133 -0
  22. package/dist/host.js +55 -0
  23. package/dist/host.js.map +1 -0
  24. package/dist/index.d.ts +20 -0
  25. package/dist/index.js +21 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/inline-token.d.ts +12 -0
  28. package/dist/inline-token.js +52 -0
  29. package/dist/inline-token.js.map +1 -0
  30. package/dist/inline-transform.d.ts +14 -0
  31. package/dist/inline-transform.js +41 -0
  32. package/dist/inline-transform.js.map +1 -0
  33. package/dist/native-drive.d.ts +100 -0
  34. package/dist/native-drive.js +318 -0
  35. package/dist/native-drive.js.map +1 -0
  36. package/dist/relative-colour.d.ts +17 -0
  37. package/dist/relative-colour.js +71 -0
  38. package/dist/relative-colour.js.map +1 -0
  39. package/dist/scroll-animation.d.ts +41 -0
  40. package/dist/scroll-animation.js +169 -0
  41. package/dist/scroll-animation.js.map +1 -0
  42. package/dist/transition.d.ts +139 -0
  43. package/dist/transition.js +569 -0
  44. package/dist/transition.js.map +1 -0
  45. package/package.json +43 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fabric.js","sourceRoot":"","sources":["../src/fabric.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,YAAY;IACZ,0BAA0B;IAC1B,uBAAuB;IACvB,kCAAkC;IAClC,aAAa;IACb,gBAAgB;IAChB,kBAAkB;IAClB,cAAc;IACd,sBAAsB;IACtB,kBAAkB;IAClB,iBAAiB;IACjB,iBAAiB;CACoC,CAAC;AAexD,MAAM,UAAU,kBAAkB;IAChC,MAAM,GAAG,GAAI,UAAkE;SAC5E,qBAAqB,CAAC;IAEzB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,gFAAgF;YAC9E,sDAAsD,CACzD,CAAC;IACJ,CAAC;IAED,gGAAgG;IAChG,4FAA4F;IAC5F,MAAM,MAAM,GAAqE,EAAE,CAAC;IACpF,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;QACxB,IAAI,OAAO,KAAK,KAAK,UAAU;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,MAAoC,CAAC;AAC9C,CAAC","sourcesContent":["/**\n * `global.nativeFabricUIManager` is a lazy caching proxy: every property access mints a\n * fresh host function. Read each method exactly once into a plain object (this is what\n * RN's own `FabricUIManager.js` does with its CACHED_PROPERTIES list).\n */\nimport type { FabricUIManager } from './engine.ts';\n\n/**\n * Every method of `FabricUIManager`, and nothing else.\n *\n * A name missing here is `undefined` on device and nowhere else, because the fake the suite runs\n * against implements the interface directly - so every test passes and every device fails. That\n * is not hypothetical: `measureInWindow` was added to the interface and to the engine and left out\n * here, and the anchored overlays it was built for could never have worked.\n *\n * `AllMethodsListed` below is what makes that impossible now rather than merely tested for.\n * Exported because the facade test iterates it: there was a second copy of this list in that file,\n * which is how the two managed to be wrong in the same way at the same time.\n */\nexport const FABRIC_METHODS = [\n 'createNode',\n 'cloneNodeWithNewChildren',\n 'cloneNodeWithNewProps',\n 'cloneNodeWithNewChildrenAndProps',\n 'appendChild',\n 'createChildSet',\n 'appendChildToSet',\n 'completeRoot',\n 'registerEventHandler',\n 'setIsJSResponder',\n 'dispatchCommand',\n 'measureInWindow',\n] as const satisfies readonly (keyof FabricUIManager)[];\n\n/**\n * A method the interface declares and the list above forgets.\n *\n * `satisfies` already checks that every name in the list is a real method. This is the other\n * direction, which is the one that bites: it resolves to `never` when the list is complete, and to\n * the missing name when it is not - so forgetting one is a compile error naming it rather than a\n * silent no-op on every phone.\n */\ntype AssertNever<T extends never> = T;\ntype AllMethodsListed = AssertNever<\n Exclude<keyof FabricUIManager, (typeof FABRIC_METHODS)[number]>\n>;\n\nexport function getFabricUIManager(): FabricUIManager {\n const raw = (globalThis as { nativeFabricUIManager?: Record<string, unknown> })\n .nativeFabricUIManager;\n\n if (!raw) {\n throw new Error(\n 'nativeFabricUIManager is not available. The New Architecture must be enabled, ' +\n 'and mount() must run inside an AppRegistry runnable.',\n );\n }\n\n // Typed with the check above, which is `never` when the list is complete and so adds nothing; a\n // check nothing refers to is an unused local to a consumer compiling with `noUnusedLocals`.\n const facade: Record<string, unknown> & { [name in AllMethodsListed]?: never } = {};\n for (const name of FABRIC_METHODS) {\n const value = raw[name];\n if (typeof value === 'function') facade[name] = value.bind(raw);\n }\n return facade as unknown as FabricUIManager;\n}\n"]}
@@ -0,0 +1,15 @@
1
+ /** Whether a name is a forms directive's input, which `reportUnboundFormsInput` reports. */
2
+ export declare function isFormsInput(name: string): boolean;
3
+ /** An element, as far as this report needs one: enough to name it. */
4
+ export interface NamedHostNode {
5
+ readonly name: string;
6
+ }
7
+ /**
8
+ * Reports `name` on `el` as an unbound forms directive input, once per `owner`/element/input
9
+ * triple. A no-op when `name` is not one of `FORMS_INPUTS`, so a caller can run this unconditionally
10
+ * from `setProperty` without checking first.
11
+ *
12
+ * `owner` keys the "already reported" set - each adapter passes its own engine, so two mounted
13
+ * apps (or two tests) never suppress each other's first report.
14
+ */
15
+ export declare function reportUnboundFormsInput(owner: object, el: NamedHostNode, name: string): void;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Inputs of Angular's forms directives, by the directive that takes each and where it comes from,
3
+ * and the report both `Renderer2` adapters make when one reaches them unclaimed.
4
+ *
5
+ * The names and package paths below are the one piece of Angular-specific knowledge in this
6
+ * package - everything else here stays framework-agnostic, per `ARCHITECTURE.md`. It lives here
7
+ * anyway, rather than duplicated in `@ng-native/platform` and `@ng-native/web`, because both of
8
+ * their `Renderer2`s hit exactly the same failure for exactly the same reason and neither imports
9
+ * the other: a binding reaches `setProperty` only when no directive on the element took it, and
10
+ * none of these names is a native prop or a DOM one, so one arriving there means the directive
11
+ * was never imported. Angular would say so on a real `platform-browser` bootstrap - its template
12
+ * type-checker is not in this build, and neither adapter's runtime check can help, because one
13
+ * takes any hyphenated element for a web component that may have any property and the other has
14
+ * no DOM to run its own check against. So `<text-input [formField]>` without `FormField` compiles,
15
+ * renders, and binds nothing, and the form sits empty and invalid without a word - unless
16
+ * something says so here.
17
+ */
18
+ const FORMS_INPUTS = {
19
+ formField: ['FormField', '@angular/forms/signals'],
20
+ ngModel: ['FormsModule', '@angular/forms'],
21
+ formControl: ['ReactiveFormsModule', '@angular/forms'],
22
+ formControlName: ['ReactiveFormsModule', '@angular/forms'],
23
+ formGroup: ['ReactiveFormsModule', '@angular/forms'],
24
+ };
25
+ /** Whether a name is a forms directive's input, which `reportUnboundFormsInput` reports. */
26
+ export function isFormsInput(name) {
27
+ return Object.hasOwn(FORMS_INPUTS, name);
28
+ }
29
+ /** Elements and inputs already reported, per caller, so a list of fields says it once. */
30
+ const reportedInputs = new WeakMap();
31
+ /**
32
+ * Reports `name` on `el` as an unbound forms directive input, once per `owner`/element/input
33
+ * triple. A no-op when `name` is not one of `FORMS_INPUTS`, so a caller can run this unconditionally
34
+ * from `setProperty` without checking first.
35
+ *
36
+ * `owner` keys the "already reported" set - each adapter passes its own engine, so two mounted
37
+ * apps (or two tests) never suppress each other's first report.
38
+ */
39
+ export function reportUnboundFormsInput(owner, el, name) {
40
+ const directiveEntry = FORMS_INPUTS[name];
41
+ if (!directiveEntry)
42
+ return;
43
+ const seen = reportedInputs.get(owner) ?? new Set();
44
+ reportedInputs.set(owner, seen);
45
+ const key = `${el.name} ${name}`;
46
+ if (seen.has(key))
47
+ return;
48
+ seen.add(key);
49
+ const [directive, from] = directiveEntry;
50
+ console.error(`[angular-native] Can't bind to '${name}' on <${el.name}>: no directive in its template ` +
51
+ `takes it, so it binds nothing. Add ${directive} from '${from}' to the component's ` +
52
+ `\`imports\`.`);
53
+ }
54
+ //# sourceMappingURL=forms-inputs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forms-inputs.js","sourceRoot":"","sources":["../src/forms-inputs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,YAAY,GAAyE;IACzF,SAAS,EAAE,CAAC,WAAW,EAAE,wBAAwB,CAAC;IAClD,OAAO,EAAE,CAAC,aAAa,EAAE,gBAAgB,CAAC;IAC1C,WAAW,EAAE,CAAC,qBAAqB,EAAE,gBAAgB,CAAC;IACtD,eAAe,EAAE,CAAC,qBAAqB,EAAE,gBAAgB,CAAC;IAC1D,SAAS,EAAE,CAAC,qBAAqB,EAAE,gBAAgB,CAAC;CACrD,CAAC;AAEF,4FAA4F;AAC5F,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;AAC3C,CAAC;AAOD,0FAA0F;AAC1F,MAAM,cAAc,GAAG,IAAI,OAAO,EAAuB,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAa,EAAE,EAAiB,EAAE,IAAY;IACpF,MAAM,cAAc,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC,cAAc;QAAE,OAAO;IAC5B,MAAM,IAAI,GAAG,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,GAAG,EAAU,CAAC;IAC5D,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAChC,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;IACjC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO;IAC1B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACd,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,cAAc,CAAC;IACzC,OAAO,CAAC,KAAK,CACX,mCAAmC,IAAI,SAAS,EAAE,CAAC,IAAI,kCAAkC;QACvF,sCAAsC,SAAS,UAAU,IAAI,uBAAuB;QACpF,cAAc,CACjB,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Inputs of Angular's forms directives, by the directive that takes each and where it comes from,\n * and the report both `Renderer2` adapters make when one reaches them unclaimed.\n *\n * The names and package paths below are the one piece of Angular-specific knowledge in this\n * package - everything else here stays framework-agnostic, per `ARCHITECTURE.md`. It lives here\n * anyway, rather than duplicated in `@ng-native/platform` and `@ng-native/web`, because both of\n * their `Renderer2`s hit exactly the same failure for exactly the same reason and neither imports\n * the other: a binding reaches `setProperty` only when no directive on the element took it, and\n * none of these names is a native prop or a DOM one, so one arriving there means the directive\n * was never imported. Angular would say so on a real `platform-browser` bootstrap - its template\n * type-checker is not in this build, and neither adapter's runtime check can help, because one\n * takes any hyphenated element for a web component that may have any property and the other has\n * no DOM to run its own check against. So `<text-input [formField]>` without `FormField` compiles,\n * renders, and binds nothing, and the form sits empty and invalid without a word - unless\n * something says so here.\n */\nconst FORMS_INPUTS: Readonly<Record<string, readonly [directive: string, from: string]>> = {\n formField: ['FormField', '@angular/forms/signals'],\n ngModel: ['FormsModule', '@angular/forms'],\n formControl: ['ReactiveFormsModule', '@angular/forms'],\n formControlName: ['ReactiveFormsModule', '@angular/forms'],\n formGroup: ['ReactiveFormsModule', '@angular/forms'],\n};\n\n/** Whether a name is a forms directive's input, which `reportUnboundFormsInput` reports. */\nexport function isFormsInput(name: string): boolean {\n return Object.hasOwn(FORMS_INPUTS, name);\n}\n\n/** An element, as far as this report needs one: enough to name it. */\nexport interface NamedHostNode {\n readonly name: string;\n}\n\n/** Elements and inputs already reported, per caller, so a list of fields says it once. */\nconst reportedInputs = new WeakMap<object, Set<string>>();\n\n/**\n * Reports `name` on `el` as an unbound forms directive input, once per `owner`/element/input\n * triple. A no-op when `name` is not one of `FORMS_INPUTS`, so a caller can run this unconditionally\n * from `setProperty` without checking first.\n *\n * `owner` keys the \"already reported\" set - each adapter passes its own engine, so two mounted\n * apps (or two tests) never suppress each other's first report.\n */\nexport function reportUnboundFormsInput(owner: object, el: NamedHostNode, name: string): void {\n const directiveEntry = FORMS_INPUTS[name];\n if (!directiveEntry) return;\n const seen = reportedInputs.get(owner) ?? new Set<string>();\n reportedInputs.set(owner, seen);\n const key = `${el.name} ${name}`;\n if (seen.has(key)) return;\n seen.add(key);\n const [directive, from] = directiveEntry;\n console.error(\n `[angular-native] Can't bind to '${name}' on <${el.name}>: no directive in its template ` +\n `takes it, so it binds nothing. Add ${directive} from '${from}' to the component's ` +\n `\\`imports\\`.`,\n );\n}\n"]}
package/dist/host.d.ts ADDED
@@ -0,0 +1,133 @@
1
+ /**
2
+ * The contract the shared packages actually depend on, rather than the Fabric implementation.
3
+ *
4
+ * `@ng-native/components` is meant to be renderer-agnostic: it describes a view and a press,
5
+ * and leaves the drawing to a host. In practice they were coupled to Fabric twice over - `inject(Engine)`, which is a concrete
6
+ * class whose constructor wants a `FabricUIManager`, and `EngineNode`, which is a Fabric tree
7
+ * node. A web prototype could only satisfy both by casting a hand-written object through
8
+ * `as unknown as Engine`, which typechecks and tells you nothing.
9
+ *
10
+ * So this is the seam, measured rather than guessed: every `engine.*` call and every `node.*`
11
+ * field those three packages actually make, and nothing else. `Engine` implements `HostEngine` and
12
+ * `EngineNode` extends `HostNode`, so nothing about the native path changes; a browser host
13
+ * implements the same two and needs no cast.
14
+ *
15
+ * It lives in this package because everything already imports from here and importing it costs a
16
+ * web build nothing - the Fabric bindings are reached lazily, through a global the browser does
17
+ * not have. If that stops being true, moving these two declarations to a package of their own is
18
+ * mechanical: nothing here refers to Fabric.
19
+ *
20
+ * ## What is deliberately not in it
21
+ *
22
+ * `tagOf` and `shadowNodeOf` hand out Fabric's own handles, for Reanimated worklets and
23
+ * react-native-gesture-handler. The three components that use them - `worklet-style`,
24
+ * `worklet-scroll` and `native-gesture` - inject the concrete `Engine` and are native-only by
25
+ * nature, which is the honest place to draw that line rather than pretending a browser could
26
+ * answer.
27
+ */
28
+ import type { DrivenProperty, EventFeed, ScrollAxis, ScrollDrive, ScrollRange, StaticTransform } from './native-drive.ts';
29
+ import type { ResponderHandlers, WindowFrame } from './engine.ts';
30
+ /** What a host node has to be able to answer. `EngineNode` and a DOM element both can. */
31
+ export interface HostNode {
32
+ /** `element` or `text`, so a caller can tell a node from the words inside it. */
33
+ readonly kind: string;
34
+ /**
35
+ * The props the host is holding for this node.
36
+ *
37
+ * A real bag rather than an accessor, because `ViewBase` deletes from it: an input that a
38
+ * directive consumes has to stop being an attribute the host writes through.
39
+ */
40
+ props: Record<string, unknown>;
41
+ /** The text, when `kind` is `text`. Empty otherwise. */
42
+ text: string;
43
+ children: readonly HostNode[];
44
+ parent: HostNode | null;
45
+ /**
46
+ * Set by `claimHost` from every host primitive's constructor, so a host can tell an element a
47
+ * component owns from a bare one somebody wrote by hand.
48
+ *
49
+ * Part of the contract rather than a Fabric detail, because the shared packages are what set
50
+ * it: `ViewBase`'s constructor calls `claimHost` on every element it hosts. What a host does
51
+ * with it is its own business - Fabric warns about an unclaimed primitive name, and a browser
52
+ * has nothing to warn about.
53
+ */
54
+ claimed?: true;
55
+ /**
56
+ * The engine that made this node, when the host can say. A primitive reads it rather than
57
+ * injecting `HostEngine`, because that lookup walks every element injector up to the root and
58
+ * runs once per element on screen. A host whose nodes cannot carry it leaves it out, and the
59
+ * primitive injects as before.
60
+ */
61
+ readonly host?: HostEngine;
62
+ }
63
+ /** Something that writes its node's props once it has its inputs. See `HostEngine.settle`. */
64
+ export interface Settling {
65
+ settleProps(): void;
66
+ }
67
+ /**
68
+ * What a host has to be able to do.
69
+ *
70
+ * An abstract class rather than an interface plus an `InjectionToken`, because Angular can inject
71
+ * an abstract class directly and the alternative is two names for one thing.
72
+ */
73
+ export declare abstract class HostEngine {
74
+ /** Write a prop. `undefined` and `null` both clear it, which is how native says "default". */
75
+ abstract setProp(node: HostNode, key: string, value: unknown): void;
76
+ /** Listen for one of the host's own events. Returns its own teardown. */
77
+ abstract setEventListener(node: HostNode, topLevelType: string, fn: (event: unknown) => void): () => void;
78
+ /** Claim gestures on a node, through whatever arbitration the host has. */
79
+ abstract setResponder(node: HostNode, handlers: ResponderHandlers): () => void;
80
+ /** Where the node is on screen, asynchronously, because native answers on its own thread. */
81
+ abstract measure(node: HostNode, into: (frame: WindowFrame) => void): void;
82
+ /** Tell a native view to do something imperative: scroll, focus, blur. */
83
+ abstract dispatchCommand(node: HostNode, name: string, args?: readonly unknown[]): void;
84
+ /** Flush pending work. Native batches; a browser may not need to. */
85
+ abstract commit(): boolean;
86
+ /** The node holding focus, or null. Verified to still be attached before it is handed back. */
87
+ abstract get focused(): HostNode | null;
88
+ /** Turn whatever an app passed as an image source into what the host wants. */
89
+ abstract resolveAsset(value: unknown): unknown;
90
+ /** Turn a colour the app wrote into whatever the host draws with. */
91
+ abstract color(value: unknown): unknown;
92
+ /**
93
+ * Call `target.settleProps()` before the next commit, once the element's static inputs are set.
94
+ *
95
+ * A host primitive writes its props from `ngOnChanges` and `ngOnInit`, which run in its parent
96
+ * view's update pass. A parent that detaches itself before its first check never has one, so
97
+ * without this its pressables would take no press and carry no role or `testID`, where on the
98
+ * web a listener and an attribute are there from creation. The default settles in a microtask,
99
+ * for a host with no commit of its own to hang it on.
100
+ */
101
+ settle(target: Settling): void;
102
+ /**
103
+ * Measure a node again from what native now holds for it, in a commit of its own. For a host
104
+ * that measures when content changes, as a browser does, there is nothing to do.
105
+ */
106
+ remeasure(_node: HostNode): void;
107
+ /**
108
+ * Take the user to a node: a text input gets the cursor, and anything else is brought on
109
+ * screen. What a DOM element's own `focus()` does, for code that calls it on an element.
110
+ */
111
+ focus(_node: HostNode): void;
112
+ /**
113
+ * Scroll whatever the node is in just far enough to show all of it; `visibleBottom` is where in
114
+ * the window the visible area ends, when something such as the keyboard covers its bottom.
115
+ */
116
+ reveal(_node: HostNode, _options?: {
117
+ visibleBottom?: number;
118
+ margin?: number;
119
+ }): void;
120
+ /** Whether `driveByScroll` moves views on the native side. A host without it moves nothing. */
121
+ get drivesScroll(): boolean;
122
+ /**
123
+ * Move `view` by `scroll`'s offset along `axis` on the native side, through `range`, keeping
124
+ * `statics` in its transform. Null when the host cannot, and the caller moves the view itself.
125
+ */
126
+ driveByScroll(_view: HostNode, _scroll: HostNode, _axis: ScrollAxis, _range: ScrollRange, _statics?: readonly StaticTransform[]): ScrollDrive | null;
127
+ /**
128
+ * Move `view` by a value `source`'s native events carry, such as a keyboard's height, on the
129
+ * native side, plus `shift` when one is given, which `ScrollDrive.shift` changes. Null when the
130
+ * host cannot. See `driveByScroll`.
131
+ */
132
+ driveByEvent(_view: HostNode, _source: HostNode, _feed: EventFeed, _property: DrivenProperty, _range: ScrollRange, _statics?: readonly StaticTransform[], _shift?: number | null): ScrollDrive | null;
133
+ }
package/dist/host.js ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * What a host has to be able to do.
3
+ *
4
+ * An abstract class rather than an interface plus an `InjectionToken`, because Angular can inject
5
+ * an abstract class directly and the alternative is two names for one thing.
6
+ */
7
+ export class HostEngine {
8
+ /**
9
+ * Call `target.settleProps()` before the next commit, once the element's static inputs are set.
10
+ *
11
+ * A host primitive writes its props from `ngOnChanges` and `ngOnInit`, which run in its parent
12
+ * view's update pass. A parent that detaches itself before its first check never has one, so
13
+ * without this its pressables would take no press and carry no role or `testID`, where on the
14
+ * web a listener and an attribute are there from creation. The default settles in a microtask,
15
+ * for a host with no commit of its own to hang it on.
16
+ */
17
+ settle(target) {
18
+ queueMicrotask(() => target.settleProps());
19
+ }
20
+ /**
21
+ * Measure a node again from what native now holds for it, in a commit of its own. For a host
22
+ * that measures when content changes, as a browser does, there is nothing to do.
23
+ */
24
+ remeasure(_node) { }
25
+ /**
26
+ * Take the user to a node: a text input gets the cursor, and anything else is brought on
27
+ * screen. What a DOM element's own `focus()` does, for code that calls it on an element.
28
+ */
29
+ focus(_node) { }
30
+ /**
31
+ * Scroll whatever the node is in just far enough to show all of it; `visibleBottom` is where in
32
+ * the window the visible area ends, when something such as the keyboard covers its bottom.
33
+ */
34
+ reveal(_node, _options) { }
35
+ /** Whether `driveByScroll` moves views on the native side. A host without it moves nothing. */
36
+ get drivesScroll() {
37
+ return false;
38
+ }
39
+ /**
40
+ * Move `view` by `scroll`'s offset along `axis` on the native side, through `range`, keeping
41
+ * `statics` in its transform. Null when the host cannot, and the caller moves the view itself.
42
+ */
43
+ driveByScroll(_view, _scroll, _axis, _range, _statics) {
44
+ return null;
45
+ }
46
+ /**
47
+ * Move `view` by a value `source`'s native events carry, such as a keyboard's height, on the
48
+ * native side, plus `shift` when one is given, which `ScrollDrive.shift` changes. Null when the
49
+ * host cannot. See `driveByScroll`.
50
+ */
51
+ driveByEvent(_view, _source, _feed, _property, _range, _statics, _shift) {
52
+ return null;
53
+ }
54
+ }
55
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AA4EA;;;;;GAKG;AACH,MAAM,OAAgB,UAAU;IAwB9B;;;;;;;;OAQG;IACH,MAAM,CAAC,MAAgB;QACrB,cAAc,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED;;;OAGG;IACH,SAAS,CAAC,KAAe,IAAS,CAAC;IAEnC;;;OAGG;IACH,KAAK,CAAC,KAAe,IAAS,CAAC;IAE/B;;;OAGG;IACH,MAAM,CAAC,KAAe,EAAE,QAAsD,IAAS,CAAC;IAExF,+FAA+F;IAC/F,IAAI,YAAY;QACd,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;OAGG;IACH,aAAa,CACX,KAAe,EACf,OAAiB,EACjB,KAAiB,EACjB,MAAmB,EACnB,QAAqC;QAErC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACH,YAAY,CACV,KAAe,EACf,OAAiB,EACjB,KAAgB,EAChB,SAAyB,EACzB,MAAmB,EACnB,QAAqC,EACrC,MAAsB;QAEtB,OAAO,IAAI,CAAC;IACd,CAAC;CACF","sourcesContent":["/**\n * The contract the shared packages actually depend on, rather than the Fabric implementation.\n *\n * `@ng-native/components` is meant to be renderer-agnostic: it describes a view and a press,\n * and leaves the drawing to a host. In practice they were coupled to Fabric twice over - `inject(Engine)`, which is a concrete\n * class whose constructor wants a `FabricUIManager`, and `EngineNode`, which is a Fabric tree\n * node. A web prototype could only satisfy both by casting a hand-written object through\n * `as unknown as Engine`, which typechecks and tells you nothing.\n *\n * So this is the seam, measured rather than guessed: every `engine.*` call and every `node.*`\n * field those three packages actually make, and nothing else. `Engine` implements `HostEngine` and\n * `EngineNode` extends `HostNode`, so nothing about the native path changes; a browser host\n * implements the same two and needs no cast.\n *\n * It lives in this package because everything already imports from here and importing it costs a\n * web build nothing - the Fabric bindings are reached lazily, through a global the browser does\n * not have. If that stops being true, moving these two declarations to a package of their own is\n * mechanical: nothing here refers to Fabric.\n *\n * ## What is deliberately not in it\n *\n * `tagOf` and `shadowNodeOf` hand out Fabric's own handles, for Reanimated worklets and\n * react-native-gesture-handler. The three components that use them - `worklet-style`,\n * `worklet-scroll` and `native-gesture` - inject the concrete `Engine` and are native-only by\n * nature, which is the honest place to draw that line rather than pretending a browser could\n * answer.\n */\nimport type {\n DrivenProperty,\n EventFeed,\n ScrollAxis,\n ScrollDrive,\n ScrollRange,\n StaticTransform,\n} from './native-drive.ts';\nimport type { ResponderHandlers, WindowFrame } from './engine.ts';\n\n/** What a host node has to be able to answer. `EngineNode` and a DOM element both can. */\nexport interface HostNode {\n /** `element` or `text`, so a caller can tell a node from the words inside it. */\n readonly kind: string;\n /**\n * The props the host is holding for this node.\n *\n * A real bag rather than an accessor, because `ViewBase` deletes from it: an input that a\n * directive consumes has to stop being an attribute the host writes through.\n */\n props: Record<string, unknown>;\n /** The text, when `kind` is `text`. Empty otherwise. */\n text: string;\n children: readonly HostNode[];\n parent: HostNode | null;\n /**\n * Set by `claimHost` from every host primitive's constructor, so a host can tell an element a\n * component owns from a bare one somebody wrote by hand.\n *\n * Part of the contract rather than a Fabric detail, because the shared packages are what set\n * it: `ViewBase`'s constructor calls `claimHost` on every element it hosts. What a host does\n * with it is its own business - Fabric warns about an unclaimed primitive name, and a browser\n * has nothing to warn about.\n */\n claimed?: true;\n /**\n * The engine that made this node, when the host can say. A primitive reads it rather than\n * injecting `HostEngine`, because that lookup walks every element injector up to the root and\n * runs once per element on screen. A host whose nodes cannot carry it leaves it out, and the\n * primitive injects as before.\n */\n readonly host?: HostEngine;\n}\n\n/** Something that writes its node's props once it has its inputs. See `HostEngine.settle`. */\nexport interface Settling {\n settleProps(): void;\n}\n\n/**\n * What a host has to be able to do.\n *\n * An abstract class rather than an interface plus an `InjectionToken`, because Angular can inject\n * an abstract class directly and the alternative is two names for one thing.\n */\nexport abstract class HostEngine {\n /** Write a prop. `undefined` and `null` both clear it, which is how native says \"default\". */\n abstract setProp(node: HostNode, key: string, value: unknown): void;\n /** Listen for one of the host's own events. Returns its own teardown. */\n abstract setEventListener(\n node: HostNode,\n topLevelType: string,\n fn: (event: unknown) => void,\n ): () => void;\n /** Claim gestures on a node, through whatever arbitration the host has. */\n abstract setResponder(node: HostNode, handlers: ResponderHandlers): () => void;\n /** Where the node is on screen, asynchronously, because native answers on its own thread. */\n abstract measure(node: HostNode, into: (frame: WindowFrame) => void): void;\n /** Tell a native view to do something imperative: scroll, focus, blur. */\n abstract dispatchCommand(node: HostNode, name: string, args?: readonly unknown[]): void;\n /** Flush pending work. Native batches; a browser may not need to. */\n abstract commit(): boolean;\n /** The node holding focus, or null. Verified to still be attached before it is handed back. */\n abstract get focused(): HostNode | null;\n /** Turn whatever an app passed as an image source into what the host wants. */\n abstract resolveAsset(value: unknown): unknown;\n /** Turn a colour the app wrote into whatever the host draws with. */\n abstract color(value: unknown): unknown;\n\n /**\n * Call `target.settleProps()` before the next commit, once the element's static inputs are set.\n *\n * A host primitive writes its props from `ngOnChanges` and `ngOnInit`, which run in its parent\n * view's update pass. A parent that detaches itself before its first check never has one, so\n * without this its pressables would take no press and carry no role or `testID`, where on the\n * web a listener and an attribute are there from creation. The default settles in a microtask,\n * for a host with no commit of its own to hang it on.\n */\n settle(target: Settling): void {\n queueMicrotask(() => target.settleProps());\n }\n\n /**\n * Measure a node again from what native now holds for it, in a commit of its own. For a host\n * that measures when content changes, as a browser does, there is nothing to do.\n */\n remeasure(_node: HostNode): void {}\n\n /**\n * Take the user to a node: a text input gets the cursor, and anything else is brought on\n * screen. What a DOM element's own `focus()` does, for code that calls it on an element.\n */\n focus(_node: HostNode): void {}\n\n /**\n * Scroll whatever the node is in just far enough to show all of it; `visibleBottom` is where in\n * the window the visible area ends, when something such as the keyboard covers its bottom.\n */\n reveal(_node: HostNode, _options?: { visibleBottom?: number; margin?: number }): void {}\n\n /** Whether `driveByScroll` moves views on the native side. A host without it moves nothing. */\n get drivesScroll(): boolean {\n return false;\n }\n\n /**\n * Move `view` by `scroll`'s offset along `axis` on the native side, through `range`, keeping\n * `statics` in its transform. Null when the host cannot, and the caller moves the view itself.\n */\n driveByScroll(\n _view: HostNode,\n _scroll: HostNode,\n _axis: ScrollAxis,\n _range: ScrollRange,\n _statics?: readonly StaticTransform[],\n ): ScrollDrive | null {\n return null;\n }\n\n /**\n * Move `view` by a value `source`'s native events carry, such as a keyboard's height, on the\n * native side, plus `shift` when one is given, which `ScrollDrive.shift` changes. Null when the\n * host cannot. See `driveByScroll`.\n */\n driveByEvent(\n _view: HostNode,\n _source: HostNode,\n _feed: EventFeed,\n _property: DrivenProperty,\n _range: ScrollRange,\n _statics?: readonly StaticTransform[],\n _shift?: number | null,\n ): ScrollDrive | null {\n return null;\n }\n}\n"]}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The engine's public surface.
3
+ *
4
+ * Nothing here imports Angular, and nothing here may start to: the engine stays
5
+ * framework-agnostic (see ARCHITECTURE.md), which is what keeps the commit logic testable without Angular in
6
+ * the loop and a second adapter possible later.
7
+ *
8
+ * The CSS runtime lives here rather than in a package of its own because the cascade is driven
9
+ * from the commit walk - the resolver runs per node, per tick - and because it is as
10
+ * framework-agnostic as the rest of the engine. The build-time half of CSS is a different
11
+ * animal and lives in `@ng-native/metro`.
12
+ */
13
+ export { StyleResolver, matches, styleSheetOf, type AttributeTest, type Combinator, type Compound, type Conditions, type DeferredDeclaration, type GradientTemplate, type HslChannel, type MediaCondition, type NthTest, type RuleIndex, type StyleCache, type StyleRule, type StyleSheet, type StyleTarget, type TokenKind, type TokenValue, } from './css.ts';
14
+ export { EngineIntersectionObserver, installDeferTriggers } from './defer-triggers.ts';
15
+ export { Engine, SyntheticEvent, claimHost, declareNativeProps, markComponentHost, nativePlatform, registerHoist, registerPlatformComponents, registerViewName, type PlatformViewName, viewNameOf, type EngineNode, type EngineOptions, type EngineStats, type FabricNode, type FabricNodeSet, type FabricUIManager, type HoistOptions, type NativeSyntheticEvent, type NodeKind, type ResponderEvent, type ResponderHandlers, type TouchPayload, type ViewNameNode, type WindowFrame, } from './engine.ts';
16
+ export { reportUnboundFormsInput, type NamedHostNode } from './forms-inputs.ts';
17
+ export { HostEngine, type HostNode, type Settling } from './host.ts';
18
+ export { getFabricUIManager } from './fabric.ts';
19
+ export { pinnedRange, type DrivenProperty, type EventFeed, type NativeAnimated, type ScrollAxis, type ScrollDrive, type ScrollRange, type StaticTransform, } from './native-drive.ts';
20
+ export { bezier, interpolate, redirect, sample, settled, step, tracksOf, tween, type AnimationSpec, type Keyframe, type RunningAnimation, type Transition, type TransitionSpec, } from './transition.ts';
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The engine's public surface.
3
+ *
4
+ * Nothing here imports Angular, and nothing here may start to: the engine stays
5
+ * framework-agnostic (see ARCHITECTURE.md), which is what keeps the commit logic testable without Angular in
6
+ * the loop and a second adapter possible later.
7
+ *
8
+ * The CSS runtime lives here rather than in a package of its own because the cascade is driven
9
+ * from the commit walk - the resolver runs per node, per tick - and because it is as
10
+ * framework-agnostic as the rest of the engine. The build-time half of CSS is a different
11
+ * animal and lives in `@ng-native/metro`.
12
+ */
13
+ export { StyleResolver, matches, styleSheetOf, } from "./css.js";
14
+ export { EngineIntersectionObserver, installDeferTriggers } from "./defer-triggers.js";
15
+ export { Engine, SyntheticEvent, claimHost, declareNativeProps, markComponentHost, nativePlatform, registerHoist, registerPlatformComponents, registerViewName, viewNameOf, } from "./engine.js";
16
+ export { reportUnboundFormsInput } from "./forms-inputs.js";
17
+ export { HostEngine } from "./host.js";
18
+ export { getFabricUIManager } from "./fabric.js";
19
+ export { pinnedRange, } from "./native-drive.js";
20
+ export { bezier, interpolate, redirect, sample, settled, step, tracksOf, tween, } from "./transition.js";
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,aAAa,EACb,OAAO,EACP,YAAY,GAiBb,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,EACL,MAAM,EACN,cAAc,EACd,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,0BAA0B,EAC1B,gBAAgB,EAEhB,UAAU,GAeX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,uBAAuB,EAAsB,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,UAAU,EAAgC,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EACL,WAAW,GAQZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,MAAM,EACN,WAAW,EACX,QAAQ,EACR,MAAM,EACN,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,KAAK,GAMN,MAAM,iBAAiB,CAAC","sourcesContent":["/**\n * The engine's public surface.\n *\n * Nothing here imports Angular, and nothing here may start to: the engine stays\n * framework-agnostic (see ARCHITECTURE.md), which is what keeps the commit logic testable without Angular in\n * the loop and a second adapter possible later.\n *\n * The CSS runtime lives here rather than in a package of its own because the cascade is driven\n * from the commit walk - the resolver runs per node, per tick - and because it is as\n * framework-agnostic as the rest of the engine. The build-time half of CSS is a different\n * animal and lives in `@ng-native/metro`.\n */\nexport {\n StyleResolver,\n matches,\n styleSheetOf,\n type AttributeTest,\n type Combinator,\n type Compound,\n type Conditions,\n type DeferredDeclaration,\n type GradientTemplate,\n type HslChannel,\n type MediaCondition,\n type NthTest,\n type RuleIndex,\n type StyleCache,\n type StyleRule,\n type StyleSheet,\n type StyleTarget,\n type TokenKind,\n type TokenValue,\n} from './css.ts';\nexport { EngineIntersectionObserver, installDeferTriggers } from './defer-triggers.ts';\nexport {\n Engine,\n SyntheticEvent,\n claimHost,\n declareNativeProps,\n markComponentHost,\n nativePlatform,\n registerHoist,\n registerPlatformComponents,\n registerViewName,\n type PlatformViewName,\n viewNameOf,\n type EngineNode,\n type EngineOptions,\n type EngineStats,\n type FabricNode,\n type FabricNodeSet,\n type FabricUIManager,\n type HoistOptions,\n type NativeSyntheticEvent,\n type NodeKind,\n type ResponderEvent,\n type ResponderHandlers,\n type TouchPayload,\n type ViewNameNode,\n type WindowFrame,\n} from './engine.ts';\nexport { reportUnboundFormsInput, type NamedHostNode } from './forms-inputs.ts';\nexport { HostEngine, type HostNode, type Settling } from './host.ts';\nexport { getFabricUIManager } from './fabric.ts';\nexport {\n pinnedRange,\n type DrivenProperty,\n type EventFeed,\n type NativeAnimated,\n type ScrollAxis,\n type ScrollDrive,\n type ScrollRange,\n type StaticTransform,\n} from './native-drive.ts';\nexport {\n bezier,\n interpolate,\n redirect,\n sample,\n settled,\n step,\n tracksOf,\n tween,\n type AnimationSpec,\n type Keyframe,\n type RunningAnimation,\n type Transition,\n type TransitionSpec,\n} from './transition.ts';\n"]}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * A custom property's value set on an element at run time, `[style.--tint]="tint()"` or
3
+ * `style="--gap: 4px"`, as a token the cascade can read.
4
+ *
5
+ * A stylesheet's custom properties are converted when the app is built, where there is a CSS
6
+ * parser (`@ng-native/metro/css/values.cjs`). A bound one only exists here, where there is not,
7
+ * so this takes the shapes a binding actually holds and nothing more: a length in `px` or `%`, a
8
+ * number, a colour as React Native writes one, and a word. Anything else is kept as a word, which
9
+ * a use site that wants a length or a colour ignores, as it ignores an undefined token.
10
+ */
11
+ import type { TokenValue } from './css.ts';
12
+ export declare function tokenFromValue(value: unknown): TokenValue | undefined;
@@ -0,0 +1,52 @@
1
+ import { isNamedColor } from "./transition.js";
2
+ const PX = /^(-?\d*\.?\d+)px$/;
3
+ const PERCENT = /^-?\d*\.?\d+%$/;
4
+ const NUMBER = /^-?\d*\.?\d+$/;
5
+ /** `#fff`, `rgb()`, `rgba()`, `hsl()`, `hsla()` and `hwb()`: what React Native's `processColor` reads. */
6
+ const COLOR_FUNCTION = /^(#[\da-f]{3,8}|(rgba?|hsla?|hwb)\(.*\))$/i;
7
+ const WORD = /^-?[a-z][\w-]*$/i;
8
+ const WEIGHTS = { normal: '400', bold: '700' };
9
+ /**
10
+ * CSS takes a bare 0 wherever it takes a length, and no other bare number. A number from 1 to 1000
11
+ * is a weight as well, at the nearest hundred, which is every weight native draws.
12
+ */
13
+ function fromNumber(value) {
14
+ if (!Number.isFinite(value))
15
+ return undefined;
16
+ const weight = value >= 1 && value <= 1000
17
+ ? { weight: String(Math.min(900, Math.max(100, Math.round(value / 100) * 100))) }
18
+ : {};
19
+ return value === 0 ? { number: 0, length: 0 } : { number: value, ...weight };
20
+ }
21
+ /**
22
+ * A word is a keyword, `row`, and may be a colour, `red`, or a weight, `bold`, as well: every form
23
+ * it can take, as the build-time conversion gives, and a use site reads the one it needs.
24
+ */
25
+ function fromWord(text) {
26
+ const weight = WEIGHTS[text.toLowerCase()];
27
+ return {
28
+ keyword: text,
29
+ ...(isNamedColor(text) ? { color: text } : {}),
30
+ ...(weight ? { weight } : {}),
31
+ };
32
+ }
33
+ export function tokenFromValue(value) {
34
+ if (typeof value === 'number')
35
+ return fromNumber(value);
36
+ if (typeof value !== 'string')
37
+ return undefined;
38
+ const text = value.trim();
39
+ if (!text)
40
+ return undefined;
41
+ const px = PX.exec(text);
42
+ if (px)
43
+ return { length: Number(px[1]) };
44
+ if (PERCENT.test(text))
45
+ return { length: text };
46
+ if (NUMBER.test(text))
47
+ return fromNumber(Number(text));
48
+ if (COLOR_FUNCTION.test(text))
49
+ return { color: text };
50
+ return WORD.test(text) ? fromWord(text) : { keyword: text };
51
+ }
52
+ //# sourceMappingURL=inline-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline-token.js","sourceRoot":"","sources":["../src/inline-token.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,EAAE,GAAG,mBAAmB,CAAC;AAC/B,MAAM,OAAO,GAAG,gBAAgB,CAAC;AACjC,MAAM,MAAM,GAAG,eAAe,CAAC;AAC/B,0GAA0G;AAC1G,MAAM,cAAc,GAAG,4CAA4C,CAAC;AACpE,MAAM,IAAI,GAAG,kBAAkB,CAAC;AAChC,MAAM,OAAO,GAA2B,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAEvE;;;GAGG;AACH,SAAS,UAAU,CAAC,KAAa;IAC/B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9C,MAAM,MAAM,GACV,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI;QACzB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE;QACjF,CAAC,CAAC,EAAE,CAAC;IACT,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,EAAE,CAAC;AAC/E,CAAC;AAED;;;GAGG;AACH,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAC3C,OAAO;QACL,OAAO,EAAE,IAAI;QACb,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9C,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC;IACxD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC1B,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzB,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACzC,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAChD,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IACvD,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACtD,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC9D,CAAC","sourcesContent":["/**\n * A custom property's value set on an element at run time, `[style.--tint]=\"tint()\"` or\n * `style=\"--gap: 4px\"`, as a token the cascade can read.\n *\n * A stylesheet's custom properties are converted when the app is built, where there is a CSS\n * parser (`@ng-native/metro/css/values.cjs`). A bound one only exists here, where there is not,\n * so this takes the shapes a binding actually holds and nothing more: a length in `px` or `%`, a\n * number, a colour as React Native writes one, and a word. Anything else is kept as a word, which\n * a use site that wants a length or a colour ignores, as it ignores an undefined token.\n */\nimport type { TokenValue } from './css.ts';\nimport { isNamedColor } from './transition.ts';\n\nconst PX = /^(-?\\d*\\.?\\d+)px$/;\nconst PERCENT = /^-?\\d*\\.?\\d+%$/;\nconst NUMBER = /^-?\\d*\\.?\\d+$/;\n/** `#fff`, `rgb()`, `rgba()`, `hsl()`, `hsla()` and `hwb()`: what React Native's `processColor` reads. */\nconst COLOR_FUNCTION = /^(#[\\da-f]{3,8}|(rgba?|hsla?|hwb)\\(.*\\))$/i;\nconst WORD = /^-?[a-z][\\w-]*$/i;\nconst WEIGHTS: Record<string, string> = { normal: '400', bold: '700' };\n\n/**\n * CSS takes a bare 0 wherever it takes a length, and no other bare number. A number from 1 to 1000\n * is a weight as well, at the nearest hundred, which is every weight native draws.\n */\nfunction fromNumber(value: number): TokenValue | undefined {\n if (!Number.isFinite(value)) return undefined;\n const weight =\n value >= 1 && value <= 1000\n ? { weight: String(Math.min(900, Math.max(100, Math.round(value / 100) * 100))) }\n : {};\n return value === 0 ? { number: 0, length: 0 } : { number: value, ...weight };\n}\n\n/**\n * A word is a keyword, `row`, and may be a colour, `red`, or a weight, `bold`, as well: every form\n * it can take, as the build-time conversion gives, and a use site reads the one it needs.\n */\nfunction fromWord(text: string): TokenValue {\n const weight = WEIGHTS[text.toLowerCase()];\n return {\n keyword: text,\n ...(isNamedColor(text) ? { color: text } : {}),\n ...(weight ? { weight } : {}),\n };\n}\n\nexport function tokenFromValue(value: unknown): TokenValue | undefined {\n if (typeof value === 'number') return fromNumber(value);\n if (typeof value !== 'string') return undefined;\n const text = value.trim();\n if (!text) return undefined;\n const px = PX.exec(text);\n if (px) return { length: Number(px[1]) };\n if (PERCENT.test(text)) return { length: text };\n if (NUMBER.test(text)) return fromNumber(Number(text));\n if (COLOR_FUNCTION.test(text)) return { color: text };\n return WORD.test(text) ? fromWord(text) : { keyword: text };\n}\n"]}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * A CSS transform string, as the list React Native's Fabric reads.
3
+ *
4
+ * A class's transform is compiled to this list at build time. A bound style -
5
+ * `[style.transform]="'rotate(' + angle + 'deg)'"` - has no build step, and Fabric drops a string
6
+ * without a word. React Native's JavaScript would have run `processTransform` over it first; there
7
+ * is none of that JavaScript between this engine and Fabric, so this does the same job.
8
+ *
9
+ * Lengths in pixels become numbers, angles and percentages stay strings, as React Native keeps
10
+ * them. A two-argument `translate`, `scale` or `skew` is split into its axes, and a 2D `matrix()`
11
+ * is widened to the sixteen values native wants.
12
+ */
13
+ export type TransformEntry = Readonly<Record<string, number | string | readonly number[]>>;
14
+ export declare function transformList(value: string): TransformEntry[];
@@ -0,0 +1,41 @@
1
+ const AXES = {
2
+ translate: ['translateX', 'translateY'],
3
+ scale: ['scaleX', 'scaleY'],
4
+ skew: ['skewX', 'skewY'],
5
+ };
6
+ const PX = /^-?\d*\.?\d+(px)?$/;
7
+ function argument(raw) {
8
+ return PX.test(raw) ? parseFloat(raw) : raw;
9
+ }
10
+ export function transformList(value) {
11
+ const out = [];
12
+ if (value.trim() === 'none')
13
+ return out;
14
+ for (const [, name, body] of value.matchAll(/([a-zA-Z0-9]+)\(([^)]*)\)/g)) {
15
+ const args = body
16
+ .split(/[\s,]+/)
17
+ .filter(Boolean)
18
+ .map(argument);
19
+ const axes = AXES[name];
20
+ if (axes && args.length === 2) {
21
+ out.push({ [axes[0]]: args[0] }, { [axes[1]]: args[1] });
22
+ }
23
+ else if (axes && name !== 'scale') {
24
+ // `translate(4px)` and `skew(10deg)` move along the first axis only; `scale(2)` is both,
25
+ // which React Native spells as a key of its own and falls through to below.
26
+ out.push({ [axes[0]]: args[0] });
27
+ }
28
+ else if (name === 'matrix' && args.length === 6) {
29
+ const [a, b, c, d, e, f] = args;
30
+ out.push({ matrix: [a, b, 0, 0, c, d, 0, 0, 0, 0, 1, 0, e, f, 0, 1] });
31
+ }
32
+ else if (name === 'matrix3d') {
33
+ out.push({ matrix: args });
34
+ }
35
+ else {
36
+ out.push({ [name]: args[0] });
37
+ }
38
+ }
39
+ return out;
40
+ }
41
+ //# sourceMappingURL=inline-transform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline-transform.js","sourceRoot":"","sources":["../src/inline-transform.ts"],"names":[],"mappings":"AAcA,MAAM,IAAI,GAAwD;IAChE,SAAS,EAAE,CAAC,YAAY,EAAE,YAAY,CAAC;IACvC,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC3B,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,EAAE,GAAG,oBAAoB,CAAC;AAEhC,SAAS,QAAQ,CAAC,GAAW;IAC3B,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,MAAM,GAAG,GAAqB,EAAE,CAAC;IACjC,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,MAAM;QAAE,OAAO,GAAG,CAAC;IACxC,KAAK,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,4BAA4B,CAAC,EAAE,CAAC;QAC1E,MAAM,IAAI,GAAG,IAAK;aACf,KAAK,CAAC,QAAQ,CAAC;aACf,MAAM,CAAC,OAAO,CAAC;aACf,GAAG,CAAC,QAAQ,CAAC,CAAC;QACjB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAK,CAAC,CAAC;QACzB,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAE,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAE,EAAE,CAAC,CAAC;QAC7D,CAAC;aAAM,IAAI,IAAI,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACpC,yFAAyF;YACzF,4EAA4E;YAC5E,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAE,EAAE,CAAC,CAAC;QACpC,CAAC;aAAM,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClD,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAgB,CAAC;YAC5C,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAE,EAAE,CAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAE,EAAE,CAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAE,EAAE,CAAE,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/E,CAAC;aAAM,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;YAC/B,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAgB,EAAE,CAAC,CAAC;QACzC,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAK,CAAC,EAAE,IAAI,CAAC,CAAC,CAAE,EAAE,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["/**\n * A CSS transform string, as the list React Native's Fabric reads.\n *\n * A class's transform is compiled to this list at build time. A bound style -\n * `[style.transform]=\"'rotate(' + angle + 'deg)'\"` - has no build step, and Fabric drops a string\n * without a word. React Native's JavaScript would have run `processTransform` over it first; there\n * is none of that JavaScript between this engine and Fabric, so this does the same job.\n *\n * Lengths in pixels become numbers, angles and percentages stay strings, as React Native keeps\n * them. A two-argument `translate`, `scale` or `skew` is split into its axes, and a 2D `matrix()`\n * is widened to the sixteen values native wants.\n */\nexport type TransformEntry = Readonly<Record<string, number | string | readonly number[]>>;\n\nconst AXES: Readonly<Record<string, readonly [string, string]>> = {\n translate: ['translateX', 'translateY'],\n scale: ['scaleX', 'scaleY'],\n skew: ['skewX', 'skewY'],\n};\n\nconst PX = /^-?\\d*\\.?\\d+(px)?$/;\n\nfunction argument(raw: string): number | string {\n return PX.test(raw) ? parseFloat(raw) : raw;\n}\n\nexport function transformList(value: string): TransformEntry[] {\n const out: TransformEntry[] = [];\n if (value.trim() === 'none') return out;\n for (const [, name, body] of value.matchAll(/([a-zA-Z0-9]+)\\(([^)]*)\\)/g)) {\n const args = body!\n .split(/[\\s,]+/)\n .filter(Boolean)\n .map(argument);\n const axes = AXES[name!];\n if (axes && args.length === 2) {\n out.push({ [axes[0]]: args[0]! }, { [axes[1]]: args[1]! });\n } else if (axes && name !== 'scale') {\n // `translate(4px)` and `skew(10deg)` move along the first axis only; `scale(2)` is both,\n // which React Native spells as a key of its own and falls through to below.\n out.push({ [axes[0]]: args[0]! });\n } else if (name === 'matrix' && args.length === 6) {\n const [a, b, c, d, e, f] = args as number[];\n out.push({ matrix: [a!, b!, 0, 0, c!, d!, 0, 0, 0, 0, 1, 0, e!, f!, 0, 1] });\n } else if (name === 'matrix3d') {\n out.push({ matrix: args as number[] });\n } else {\n out.push({ [name!]: args[0]! });\n }\n }\n return out;\n}\n"]}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * A view moved by a scroll view's offset on the native side, with no JavaScript in between.
3
+ *
4
+ * A sticky header moved by a transform set from each scroll event lags the scroll: the event
5
+ * reaches JavaScript, the transform is committed, and by the time Fabric mounts it the content has
6
+ * moved on, so on a fast fling the header shudders and comes away from the edge it is pinned to.
7
+ * React Native's own sticky headers do not, because `ScrollViewStickyHeader` maps the offset onto
8
+ * an animated value on the native side and interpolates the header's translate from it there.
9
+ * This is that mapping, for the engine's nodes: the scroll view's offset along one axis feeds a
10
+ * value, the value an interpolation, the interpolation a translate, and native applies it on every
11
+ * frame the scroll view moves.
12
+ *
13
+ * It goes through React Native's own `NativeAnimatedHelper`, whose node tags every other animated
14
+ * value in the app also comes from, so none of them can collide.
15
+ */
16
+ /** The part of React Native's `NativeAnimatedHelper` default export this uses. */
17
+ export interface NativeAnimated {
18
+ generateNewNodeTag(): number;
19
+ readonly API: {
20
+ createAnimatedNode(tag: number, config: object): void;
21
+ connectAnimatedNodes(parentTag: number, childTag: number): void;
22
+ disconnectAnimatedNodes(parentTag: number, childTag: number): void;
23
+ connectAnimatedNodeToView(nodeTag: number, viewTag: number): void;
24
+ disconnectAnimatedNodeFromView(nodeTag: number, viewTag: number): void;
25
+ dropAnimatedNode(tag: number): void;
26
+ addAnimatedEventToView(viewTag: number, eventName: string, mapping: {
27
+ nativeEventPath: string[];
28
+ animatedValueTag: number;
29
+ }): void;
30
+ removeAnimatedEventFromView(viewTag: number, eventName: string, nodeTag: number): void;
31
+ setAnimatedNodeValue(nodeTag: number, value: number): void;
32
+ flushQueue?(): void;
33
+ };
34
+ }
35
+ import type { DrivenChannels } from './scroll-animation.ts';
36
+ export type ScrollAxis = 'x' | 'y';
37
+ /** A native event that feeds a value: the events carrying it, and the path to it in each. */
38
+ export interface EventFeed {
39
+ readonly events: readonly string[];
40
+ readonly path: readonly string[];
41
+ }
42
+ /** What a driven view moves along. */
43
+ export type DrivenProperty = 'translateX' | 'translateY';
44
+ /** Where a driven view sits for each scroll offset, as an interpolation clamped at both ends. */
45
+ export interface ScrollRange {
46
+ readonly input: readonly number[];
47
+ readonly output: readonly number[];
48
+ }
49
+ /** A transform native keeps beside the driven translate, such as an inverted list's flip. */
50
+ export type StaticTransform = Readonly<Record<string, number>>;
51
+ /**
52
+ * A view moved by a scroll. `update` moves it by a new range; `shift` adds an amount to where the
53
+ * range puts it, for a drive made with one; `stop` lets it go.
54
+ */
55
+ export interface ScrollDrive {
56
+ update(range: ScrollRange): void;
57
+ shift(amount: number): void;
58
+ stop(): void;
59
+ }
60
+ export declare class NativeScrollDriver {
61
+ private readonly native;
62
+ /** One value per source view and feed, by the feed's key. */
63
+ private readonly offsets;
64
+ private readonly pending;
65
+ constructor(native: NativeAnimated);
66
+ /**
67
+ * Move a view by a scroll. `tagOf` answers a node's view tag once it has been committed, and
68
+ * null before, which is why the connection may wait for a commit: `connect` is tried again by
69
+ * `afterCommit` until both views exist.
70
+ */
71
+ drive(view: object, source: object, feed: EventFeed, property: DrivenProperty, range: ScrollRange, statics: readonly StaticTransform[], tagOf: (node: object) => number | null, shift?: number | null): ScrollDrive;
72
+ /**
73
+ * Play a view's opacity and transform by a scroll: each channel an interpolation from the
74
+ * offset, as `scroll-animation.ts` lays a `@keyframes` animation along it. `update` rebuilds
75
+ * the channels, when the scroll view says how far it scrolls; `stop` lets the view go.
76
+ */
77
+ animate(view: object, source: object, feed: EventFeed, channels: DrivenChannels, tagOf: (node: object) => number | null): {
78
+ update(channels: DrivenChannels): void;
79
+ stop(): void;
80
+ };
81
+ /** The nodes for a set of channels, connected to the view; the props node first. */
82
+ private buildChannels;
83
+ private dropChannels;
84
+ /** Connects every drive still waiting for its views, now that a commit may have made them. */
85
+ afterCommit(): void;
86
+ /** One value per source view and feed, fed by its events, shared by every view it drives. */
87
+ private offset;
88
+ private release;
89
+ private build;
90
+ private tearDown;
91
+ private flush;
92
+ }
93
+ /** A scroll view's offset along an axis, as `onScroll` carries it. */
94
+ export declare function scrollFeed(axis: ScrollAxis): EventFeed;
95
+ /**
96
+ * The translate a pinned view needs at each scroll offset: none until the offset reaches where
97
+ * the view starts, then the offset past that, until `until`, where the next pinned view pushes it
98
+ * off and it stays. `start` and `until` are in the scroll view's own coordinates.
99
+ */
100
+ export declare function pinnedRange(start: number, until?: number): ScrollRange;