@ng-native/fabric 0.13.0 → 0.15.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 (41) hide show
  1. package/dist/css.d.ts +63 -6
  2. package/dist/css.js +268 -59
  3. package/dist/css.js.map +1 -1
  4. package/dist/defer-triggers.js +2 -8
  5. package/dist/defer-triggers.js.map +1 -1
  6. package/dist/engine.d.ts +201 -21
  7. package/dist/engine.js +915 -125
  8. package/dist/engine.js.map +1 -1
  9. package/dist/fabric.d.ts +1 -1
  10. package/dist/fabric.js +25 -0
  11. package/dist/fabric.js.map +1 -1
  12. package/dist/font-faces.d.ts +19 -0
  13. package/dist/font-faces.js +42 -0
  14. package/dist/font-faces.js.map +1 -1
  15. package/dist/forms-inputs.d.ts +1 -1
  16. package/dist/forms-inputs.js +2 -2
  17. package/dist/forms-inputs.js.map +1 -1
  18. package/dist/index.d.ts +3 -2
  19. package/dist/index.js +2 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/inline-derived-token.js +56 -7
  22. package/dist/inline-derived-token.js.map +1 -1
  23. package/dist/inline-token.d.ts +4 -3
  24. package/dist/inline-token.js +41 -8
  25. package/dist/inline-token.js.map +1 -1
  26. package/dist/native-drive.d.ts +42 -1
  27. package/dist/native-drive.js +135 -9
  28. package/dist/native-drive.js.map +1 -1
  29. package/dist/node-global.d.ts +9 -0
  30. package/dist/node-global.js +41 -0
  31. package/dist/node-global.js.map +1 -0
  32. package/dist/relative-colour.d.ts +2 -2
  33. package/dist/relative-colour.js +4 -0
  34. package/dist/relative-colour.js.map +1 -1
  35. package/dist/scroll-animation.d.ts +12 -0
  36. package/dist/scroll-animation.js +10 -0
  37. package/dist/scroll-animation.js.map +1 -1
  38. package/dist/transition.d.ts +3 -2
  39. package/dist/transition.js +17 -8
  40. package/dist/transition.js.map +1 -1
  41. package/package.json +1 -1
package/dist/fabric.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * fresh host function. Read each method exactly once into a plain object (this is what
4
4
  * RN's own `FabricUIManager.js` does with its CACHED_PROPERTIES list).
5
5
  */
6
- import type { FabricUIManager } from './engine.ts';
6
+ import { type FabricUIManager } from './engine.ts';
7
7
  /**
8
8
  * Every method of `FabricUIManager`, and nothing else.
9
9
  *
package/dist/fabric.js CHANGED
@@ -1,3 +1,9 @@
1
+ /**
2
+ * `global.nativeFabricUIManager` is a lazy caching proxy: every property access mints a
3
+ * fresh host function. Read each method exactly once into a plain object (this is what
4
+ * RN's own `FabricUIManager.js` does with its CACHED_PROPERTIES list).
5
+ */
6
+ import { nativePlatform } from "./engine.js";
1
7
  /**
2
8
  * Every method of `FabricUIManager`, and nothing else.
3
9
  *
@@ -40,6 +46,25 @@ export function getFabricUIManager() {
40
46
  if (typeof value === 'function')
41
47
  facade[name] = value.bind(raw);
42
48
  }
49
+ // Asked for, a layout animation that is off moves nothing, and the views jump to where it ends.
50
+ if (!layoutAnimationsOn())
51
+ delete facade['configureNextLayoutAnimation'];
43
52
  return facade;
44
53
  }
54
+ /**
55
+ * Whether native plays a layout animation on this platform. React Native turns them off on
56
+ * Android unless the app turns them on, and `configureNextLayoutAnimation` is there to call either
57
+ * way. Read from React Native's feature flags module through the two globals its own
58
+ * `TurboModuleRegistry` reads, so nothing of React Native is imported. Taken as on where there is
59
+ * no flag to read.
60
+ */
61
+ function layoutAnimationsOn() {
62
+ const host = globalThis;
63
+ const name = 'NativeReactNativeFeatureFlagsCxx';
64
+ const flags = (host.__turboModuleProxy?.(name) ?? host.nativeModuleProxy?.[name]);
65
+ const flag = nativePlatform() === 'android'
66
+ ? 'enableLayoutAnimationsOnAndroid'
67
+ : 'enableLayoutAnimationsOnIOS';
68
+ return flags?.[flag]?.() !== false;
69
+ }
45
70
  //# sourceMappingURL=fabric.js.map
@@ -1 +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,8BAA8B;IAC9B,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 'configureNextLayoutAnimation',\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"]}
1
+ {"version":3,"file":"fabric.js","sourceRoot":"","sources":["../src/fabric.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,cAAc,EAAwB,MAAM,aAAa,CAAC;AAEnE;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,YAAY;IACZ,0BAA0B;IAC1B,uBAAuB;IACvB,kCAAkC;IAClC,aAAa;IACb,8BAA8B;IAC9B,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,gGAAgG;IAChG,IAAI,CAAC,kBAAkB,EAAE;QAAE,OAAO,MAAM,CAAC,8BAA8B,CAAC,CAAC;IACzE,OAAO,MAAoC,CAAC;AAC9C,CAAC;AAED;;;;;;GAMG;AACH,SAAS,kBAAkB;IACzB,MAAM,IAAI,GAAG,UAGZ,CAAC;IACF,MAAM,IAAI,GAAG,kCAAkC,CAAC;IAChD,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC,IAAI,CAAC,CAChB,CAAC;IACjE,MAAM,IAAI,GACR,cAAc,EAAE,KAAK,SAAS;QAC5B,CAAC,CAAC,iCAAiC;QACnC,CAAC,CAAC,6BAA6B,CAAC;IACpC,OAAO,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,KAAK,CAAC;AACrC,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 { nativePlatform, 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 'configureNextLayoutAnimation',\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 // Asked for, a layout animation that is off moves nothing, and the views jump to where it ends.\n if (!layoutAnimationsOn()) delete facade['configureNextLayoutAnimation'];\n return facade as unknown as FabricUIManager;\n}\n\n/**\n * Whether native plays a layout animation on this platform. React Native turns them off on\n * Android unless the app turns them on, and `configureNextLayoutAnimation` is there to call either\n * way. Read from React Native's feature flags module through the two globals its own\n * `TurboModuleRegistry` reads, so nothing of React Native is imported. Taken as on where there is\n * no flag to read.\n */\nfunction layoutAnimationsOn(): boolean {\n const host = globalThis as {\n __turboModuleProxy?: (name: string) => unknown;\n nativeModuleProxy?: Record<string, unknown>;\n };\n const name = 'NativeReactNativeFeatureFlagsCxx';\n const flags = (host.__turboModuleProxy?.(name) ?? host.nativeModuleProxy?.[name]) as\n Record<string, (() => boolean) | undefined> | null | undefined;\n const flag =\n nativePlatform() === 'android'\n ? 'enableLayoutAnimationsOnAndroid'\n : 'enableLayoutAnimationsOnIOS';\n return flags?.[flag]?.() !== false;\n}\n"]}
@@ -20,6 +20,13 @@ export declare const DROP_MATCHED_WEIGHT_AND_STYLE = true;
20
20
  * range is both its ends, `Inter-100to900`, so two files that split a family by range differ.
21
21
  */
22
22
  export declare function faceName(face: Pick<FontFace, 'family' | 'weight' | 'weightRange' | 'style'>): string;
23
+ /**
24
+ * Every name the faces are registered under, and the face each names, in the order they are
25
+ * registered. A face is registered under `faceName`, the last face of that name winning, and the
26
+ * first face of a family under the family as well. A face with both a weight and a style also
27
+ * answers to each alone, where no face declared exactly that.
28
+ */
29
+ export declare function registeredFaces<Face extends FontFace>(faces: readonly Face[]): Map<string, Face>;
23
30
  /**
24
31
  * The face CSS's matching picks among one family's, for a wanted weight and style.
25
32
  *
@@ -31,8 +38,20 @@ export declare function matchFace(faces: readonly FontFace[], weight: number, st
31
38
  /** Every face the engine has seen a sheet declare, by family. */
32
39
  export declare class FontFaces {
33
40
  private readonly byFamily;
41
+ /**
42
+ * Every face, in the order taken in, one declared twice under a name too: the name index takes
43
+ * the last of them, as expo-font registers it, where matching keeps the first.
44
+ */
45
+ private readonly faces;
46
+ /** What `postScriptName` reads, made again on the first read after the faces change. */
47
+ private names;
34
48
  /** Forget every face, before they are taken in again from the sheets still registered. */
35
49
  clear(): void;
50
+ /**
51
+ * The PostScript name of the face a name is registered under, as `registeredFaces` names them,
52
+ * where the build could read it from the face's file.
53
+ */
54
+ postScriptName(name: string): string | undefined;
36
55
  /** Take faces in; whether any was new, which changes what text already matched may match. */
37
56
  add(faces: readonly FontFace[]): boolean;
38
57
  /**
@@ -19,6 +19,29 @@ export function faceName(face) {
19
19
  name += `-${face.style}`;
20
20
  return name;
21
21
  }
22
+ /**
23
+ * Every name the faces are registered under, and the face each names, in the order they are
24
+ * registered. A face is registered under `faceName`, the last face of that name winning, and the
25
+ * first face of a family under the family as well. A face with both a weight and a style also
26
+ * answers to each alone, where no face declared exactly that.
27
+ */
28
+ export function registeredFaces(faces) {
29
+ const names = new Map();
30
+ for (const face of faces) {
31
+ if (!names.has(face.family))
32
+ names.set(face.family, face);
33
+ names.set(faceName(face), face);
34
+ }
35
+ for (const face of faces) {
36
+ if (face.weight === undefined || !face.style)
37
+ continue;
38
+ for (const name of [`${face.family}-${face.weight}`, `${face.family}-${face.style}`]) {
39
+ if (!names.has(name))
40
+ names.set(name, face);
41
+ }
42
+ }
43
+ return names;
44
+ }
22
45
  /** CSS's fallback order for a style: the closest slant first, upright last. */
23
46
  const STYLE_ORDER = {
24
47
  italic: ['italic', 'oblique', 'normal'],
@@ -67,13 +90,32 @@ const familyKey = (family) => family.toLowerCase();
67
90
  /** Every face the engine has seen a sheet declare, by family. */
68
91
  export class FontFaces {
69
92
  byFamily = new Map();
93
+ /**
94
+ * Every face, in the order taken in, one declared twice under a name too: the name index takes
95
+ * the last of them, as expo-font registers it, where matching keeps the first.
96
+ */
97
+ faces = [];
98
+ /** What `postScriptName` reads, made again on the first read after the faces change. */
99
+ names = null;
70
100
  /** Forget every face, before they are taken in again from the sheets still registered. */
71
101
  clear() {
72
102
  this.byFamily.clear();
103
+ this.faces.length = 0;
104
+ this.names = null;
105
+ }
106
+ /**
107
+ * The PostScript name of the face a name is registered under, as `registeredFaces` names them,
108
+ * where the build could read it from the face's file.
109
+ */
110
+ postScriptName(name) {
111
+ this.names ??= registeredFaces(this.faces);
112
+ return this.names.get(name)?.postScriptName;
73
113
  }
74
114
  /** Take faces in; whether any was new, which changes what text already matched may match. */
75
115
  add(faces) {
76
116
  let added = false;
117
+ this.faces.push(...faces);
118
+ this.names = null;
77
119
  for (const face of faces) {
78
120
  const known = this.byFamily.get(familyKey(face.family));
79
121
  if (known?.some((one) => faceName(one) === faceName(face)))
@@ -1 +1 @@
1
- {"version":3,"file":"font-faces.js","sourceRoot":"","sources":["../src/font-faces.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Font matching, which native does not do.\n *\n * A device finds a family by name and nothing else, so each `@font-face` is registered under a\n * name of its own (`faceName`), and a text asking for a declared family with a weight or a style\n * is pointed at the face CSS would pick for it. That happens on the resolved style, after the\n * cascade, because a family and a weight often come from different rules or from an ancestor.\n */\nimport type { StyleSheet } from './css.ts';\n\ntype FontFace = NonNullable<StyleSheet['fonts']>[number];\n\n/**\n * When a face was picked, whether to drop the `fontWeight` and `fontStyle` it was picked for from\n * what is committed. The face already draws them, and kept, the platform may embolden or slant it\n * again on top.\n */\nexport const DROP_MATCHED_WEIGHT_AND_STYLE = true;\n\n/**\n * The name a face is registered under: the family, then its weight and its style where it\n * declares them. `Inter`, `Inter-600`, `Inter-italic`, `Inter-600-italic`. A variable font's\n * range is both its ends, `Inter-100to900`, so two files that split a family by range differ.\n */\nexport function faceName(\n face: Pick<FontFace, 'family' | 'weight' | 'weightRange' | 'style'>,\n): string {\n let name = face.family;\n if (face.weight !== undefined) name += `-${face.weight}`;\n else if (face.weightRange) name += `-${face.weightRange[0]}to${face.weightRange[1]}`;\n if (face.style) name += `-${face.style}`;\n return name;\n}\n\n/** CSS's fallback order for a style: the closest slant first, upright last. */\nconst STYLE_ORDER: Readonly<Record<string, readonly string[]>> = {\n italic: ['italic', 'oblique', 'normal'],\n oblique: ['oblique', 'italic', 'normal'],\n normal: ['normal', 'oblique', 'italic'],\n};\n\n/** A `fontWeight` as a number: `normal` and unset are 400, `bold` 700. NaN for anything else. */\nfunction weightOf(value: unknown): number {\n if (value === undefined || value === 'normal') return 400;\n if (value === 'bold') return 700;\n return Number(value);\n}\n\n/**\n * The face CSS's matching picks among one family's, for a wanted weight and style.\n *\n * Style first, then weight, as in CSS Fonts 4 section 5.2. For a weight from 400 to 500, the\n * weights up to 500 are tried first, then lighter ones, then heavier; under 400, lighter then\n * heavier; over 500, heavier then lighter. Each direction nearest first.\n */\nexport function matchFace(\n faces: readonly FontFace[],\n weight: number,\n style: string,\n): FontFace | undefined {\n const slant = (face: FontFace) => face.style ?? 'normal';\n const order = STYLE_ORDER[style] ?? STYLE_ORDER['normal']!;\n const wantedStyle = order.find((one) => faces.some((face) => slant(face) === one));\n const candidates = faces.filter((face) => slant(face) === wantedStyle);\n\n // A face that covers a range has every weight in it: the nearest it has to the one wanted.\n const weightOfFace = (face: FontFace) =>\n face.weightRange\n ? Math.min(Math.max(weight, face.weightRange[0]), face.weightRange[1])\n : (face.weight ?? 400);\n const heavier = (from: number, to: number) =>\n candidates\n .filter((face) => weightOfFace(face) >= from && weightOfFace(face) <= to)\n .sort((a, b) => weightOfFace(a) - weightOfFace(b))[0];\n const lighter = (upTo: number) =>\n candidates\n .filter((face) => weightOfFace(face) <= upTo)\n .sort((a, b) => weightOfFace(b) - weightOfFace(a))[0];\n\n if (weight >= 400 && weight <= 500) {\n return heavier(weight, 500) ?? lighter(weight) ?? heavier(500, Infinity);\n }\n if (weight < 400) return lighter(weight) ?? heavier(weight, Infinity);\n return heavier(weight, Infinity) ?? lighter(weight);\n}\n\n/** A family as CSS compares it: without regard to case. */\nconst familyKey = (family: string) => family.toLowerCase();\n\n/** Every face the engine has seen a sheet declare, by family. */\nexport class FontFaces {\n private readonly byFamily = new Map<string, FontFace[]>();\n\n /** Forget every face, before they are taken in again from the sheets still registered. */\n clear(): void {\n this.byFamily.clear();\n }\n\n /** Take faces in; whether any was new, which changes what text already matched may match. */\n add(faces: readonly FontFace[]): boolean {\n let added = false;\n for (const face of faces) {\n const known = this.byFamily.get(familyKey(face.family));\n if (known?.some((one) => faceName(one) === faceName(face))) continue;\n if (known) known.push(face);\n else this.byFamily.set(familyKey(face.family), [face]);\n added = true;\n }\n return added;\n }\n\n /**\n * Point a resolved style's family at the face its weight and style ask for. A family nothing\n * declared is left alone, and so is the family of one whose match is the face registered under\n * the family itself.\n */\n apply(style: Record<string, unknown>): void {\n if (this.byFamily.size === 0) return;\n const family = style['fontFamily'];\n if (typeof family !== 'string') return;\n const faces = this.byFamily.get(familyKey(family));\n if (!faces) return;\n\n const weight = weightOf(style['fontWeight']);\n if (Number.isNaN(weight)) return;\n const face = matchFace(faces, weight, String(style['fontStyle'] ?? 'normal'));\n if (!face) return;\n const name = faceName(face);\n if (name !== family) style['fontFamily'] = name;\n\n if (!DROP_MATCHED_WEIGHT_AND_STYLE) return;\n // The style goes whichever face matched: a slanted face draws its slant, and beside an\n // upright one Android draws its system font slanted in place of the face, which iOS draws\n // upright anyway.\n delete style['fontStyle'];\n // A face of one weight draws it. One that covers a range is told which, so the weight stays.\n if (face.weight !== undefined) delete style['fontWeight'];\n }\n}\n"]}
1
+ {"version":3,"file":"font-faces.js","sourceRoot":"","sources":["../src/font-faces.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Font matching, which native does not do.\n *\n * A device finds a family by name and nothing else, so each `@font-face` is registered under a\n * name of its own (`faceName`), and a text asking for a declared family with a weight or a style\n * is pointed at the face CSS would pick for it. That happens on the resolved style, after the\n * cascade, because a family and a weight often come from different rules or from an ancestor.\n */\nimport type { StyleSheet } from './css.ts';\n\ntype FontFace = NonNullable<StyleSheet['fonts']>[number];\n\n/**\n * When a face was picked, whether to drop the `fontWeight` and `fontStyle` it was picked for from\n * what is committed. The face already draws them, and kept, the platform may embolden or slant it\n * again on top.\n */\nexport const DROP_MATCHED_WEIGHT_AND_STYLE = true;\n\n/**\n * The name a face is registered under: the family, then its weight and its style where it\n * declares them. `Inter`, `Inter-600`, `Inter-italic`, `Inter-600-italic`. A variable font's\n * range is both its ends, `Inter-100to900`, so two files that split a family by range differ.\n */\nexport function faceName(\n face: Pick<FontFace, 'family' | 'weight' | 'weightRange' | 'style'>,\n): string {\n let name = face.family;\n if (face.weight !== undefined) name += `-${face.weight}`;\n else if (face.weightRange) name += `-${face.weightRange[0]}to${face.weightRange[1]}`;\n if (face.style) name += `-${face.style}`;\n return name;\n}\n\n/**\n * Every name the faces are registered under, and the face each names, in the order they are\n * registered. A face is registered under `faceName`, the last face of that name winning, and the\n * first face of a family under the family as well. A face with both a weight and a style also\n * answers to each alone, where no face declared exactly that.\n */\nexport function registeredFaces<Face extends FontFace>(faces: readonly Face[]): Map<string, Face> {\n const names = new Map<string, Face>();\n for (const face of faces) {\n if (!names.has(face.family)) names.set(face.family, face);\n names.set(faceName(face), face);\n }\n for (const face of faces) {\n if (face.weight === undefined || !face.style) continue;\n for (const name of [`${face.family}-${face.weight}`, `${face.family}-${face.style}`]) {\n if (!names.has(name)) names.set(name, face);\n }\n }\n return names;\n}\n\n/** CSS's fallback order for a style: the closest slant first, upright last. */\nconst STYLE_ORDER: Readonly<Record<string, readonly string[]>> = {\n italic: ['italic', 'oblique', 'normal'],\n oblique: ['oblique', 'italic', 'normal'],\n normal: ['normal', 'oblique', 'italic'],\n};\n\n/** A `fontWeight` as a number: `normal` and unset are 400, `bold` 700. NaN for anything else. */\nfunction weightOf(value: unknown): number {\n if (value === undefined || value === 'normal') return 400;\n if (value === 'bold') return 700;\n return Number(value);\n}\n\n/**\n * The face CSS's matching picks among one family's, for a wanted weight and style.\n *\n * Style first, then weight, as in CSS Fonts 4 section 5.2. For a weight from 400 to 500, the\n * weights up to 500 are tried first, then lighter ones, then heavier; under 400, lighter then\n * heavier; over 500, heavier then lighter. Each direction nearest first.\n */\nexport function matchFace(\n faces: readonly FontFace[],\n weight: number,\n style: string,\n): FontFace | undefined {\n const slant = (face: FontFace) => face.style ?? 'normal';\n const order = STYLE_ORDER[style] ?? STYLE_ORDER['normal']!;\n const wantedStyle = order.find((one) => faces.some((face) => slant(face) === one));\n const candidates = faces.filter((face) => slant(face) === wantedStyle);\n\n // A face that covers a range has every weight in it: the nearest it has to the one wanted.\n const weightOfFace = (face: FontFace) =>\n face.weightRange\n ? Math.min(Math.max(weight, face.weightRange[0]), face.weightRange[1])\n : (face.weight ?? 400);\n const heavier = (from: number, to: number) =>\n candidates\n .filter((face) => weightOfFace(face) >= from && weightOfFace(face) <= to)\n .sort((a, b) => weightOfFace(a) - weightOfFace(b))[0];\n const lighter = (upTo: number) =>\n candidates\n .filter((face) => weightOfFace(face) <= upTo)\n .sort((a, b) => weightOfFace(b) - weightOfFace(a))[0];\n\n if (weight >= 400 && weight <= 500) {\n return heavier(weight, 500) ?? lighter(weight) ?? heavier(500, Infinity);\n }\n if (weight < 400) return lighter(weight) ?? heavier(weight, Infinity);\n return heavier(weight, Infinity) ?? lighter(weight);\n}\n\n/** A family as CSS compares it: without regard to case. */\nconst familyKey = (family: string) => family.toLowerCase();\n\n/** Every face the engine has seen a sheet declare, by family. */\nexport class FontFaces {\n private readonly byFamily = new Map<string, FontFace[]>();\n /**\n * Every face, in the order taken in, one declared twice under a name too: the name index takes\n * the last of them, as expo-font registers it, where matching keeps the first.\n */\n private readonly faces: FontFace[] = [];\n /** What `postScriptName` reads, made again on the first read after the faces change. */\n private names: Map<string, FontFace> | null = null;\n\n /** Forget every face, before they are taken in again from the sheets still registered. */\n clear(): void {\n this.byFamily.clear();\n this.faces.length = 0;\n this.names = null;\n }\n\n /**\n * The PostScript name of the face a name is registered under, as `registeredFaces` names them,\n * where the build could read it from the face's file.\n */\n postScriptName(name: string): string | undefined {\n this.names ??= registeredFaces(this.faces);\n return this.names.get(name)?.postScriptName;\n }\n\n /** Take faces in; whether any was new, which changes what text already matched may match. */\n add(faces: readonly FontFace[]): boolean {\n let added = false;\n this.faces.push(...faces);\n this.names = null;\n for (const face of faces) {\n const known = this.byFamily.get(familyKey(face.family));\n if (known?.some((one) => faceName(one) === faceName(face))) continue;\n if (known) known.push(face);\n else this.byFamily.set(familyKey(face.family), [face]);\n added = true;\n }\n return added;\n }\n\n /**\n * Point a resolved style's family at the face its weight and style ask for. A family nothing\n * declared is left alone, and so is the family of one whose match is the face registered under\n * the family itself.\n */\n apply(style: Record<string, unknown>): void {\n if (this.byFamily.size === 0) return;\n const family = style['fontFamily'];\n if (typeof family !== 'string') return;\n const faces = this.byFamily.get(familyKey(family));\n if (!faces) return;\n\n const weight = weightOf(style['fontWeight']);\n if (Number.isNaN(weight)) return;\n const face = matchFace(faces, weight, String(style['fontStyle'] ?? 'normal'));\n if (!face) return;\n const name = faceName(face);\n if (name !== family) style['fontFamily'] = name;\n\n if (!DROP_MATCHED_WEIGHT_AND_STYLE) return;\n // The style goes whichever face matched: a slanted face draws its slant, and beside an\n // upright one Android draws its system font slanted in place of the face, which iOS draws\n // upright anyway.\n delete style['fontStyle'];\n // A face of one weight draws it. One that covers a range is told which, so the weight stays.\n if (face.weight !== undefined) delete style['fontWeight'];\n }\n}\n"]}
@@ -2,7 +2,7 @@
2
2
  export declare function isFormsInput(name: string): boolean;
3
3
  /** An element, as far as this report needs one: enough to name it. */
4
4
  export interface NamedHostNode {
5
- readonly name: string;
5
+ readonly tag: string;
6
6
  }
7
7
  /**
8
8
  * Reports `name` on `el` as an unbound forms directive input, once per `owner`/element/input
@@ -42,12 +42,12 @@ export function reportUnboundFormsInput(owner, el, name) {
42
42
  return;
43
43
  const seen = reportedInputs.get(owner) ?? new Set();
44
44
  reportedInputs.set(owner, seen);
45
- const key = `${el.name} ${name}`;
45
+ const key = `${el.tag} ${name}`;
46
46
  if (seen.has(key))
47
47
  return;
48
48
  seen.add(key);
49
49
  const [directive, from] = directiveEntry;
50
- console.error(`[angular-native] Can't bind to '${name}' on <${el.name}>: no directive in its template ` +
50
+ console.error(`[angular-native] Can't bind to '${name}' on <${el.tag}>: no directive in its template ` +
51
51
  `takes it, so it binds nothing. Add ${directive} from '${from}' to the component's ` +
52
52
  `\`imports\`.`);
53
53
  }
@@ -1 +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 `docs/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"]}
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,GAAG,IAAI,IAAI,EAAE,CAAC;IAChC,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,GAAG,kCAAkC;QACtF,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 `docs/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 tag: 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.tag} ${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.tag}>: 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/index.d.ts CHANGED
@@ -13,9 +13,10 @@
13
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
14
  export { ELEMENT_STYLES, ELEMENT_STYLES_CSS } from './element-styles.ts';
15
15
  export { EngineIntersectionObserver, installDeferTriggers } from './defer-triggers.ts';
16
- export { faceName } from './font-faces.ts';
16
+ export { installNodeGlobal } from './node-global.ts';
17
+ export { faceName, registeredFaces } from './font-faces.ts';
17
18
  export { firstFamily } from './inline-token.ts';
18
- export { DIRECT_EVENTS, Engine, SyntheticEvent, claimHost, keepNativeView, declareNativeProps, fontsLoading, fontsRegistered, fontsSettled, markComponentHost, nativePlatform, onFontsRegistered, onFontsSettled, registerHoist, registerPlatformComponents, registerViewName, type PlatformViewName, type ViewNameOptions, 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';
19
+ export { DIRECT_EVENTS, Engine, SyntheticEvent, claimHost, keepNativeView, declareNativeProps, fontsLoading, fontsRegistered, fontsSettled, markComponentHost, nativePlatform, onFontsRegistered, onFontsSettled, registerHoist, registerPlatformComponents, registerViewName, type PlatformViewName, type ViewNameOptions, type PostScriptNames, 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';
19
20
  export { reportUnboundFormsInput, type NamedHostNode } from './forms-inputs.ts';
20
21
  export { HostEngine, type HostNode, type Settling } from './host.ts';
21
22
  export { getFabricUIManager } from './fabric.ts';
package/dist/index.js CHANGED
@@ -13,7 +13,8 @@
13
13
  export { StyleResolver, matches, styleSheetOf, } from "./css.js";
14
14
  export { ELEMENT_STYLES, ELEMENT_STYLES_CSS } from "./element-styles.js";
15
15
  export { EngineIntersectionObserver, installDeferTriggers } from "./defer-triggers.js";
16
- export { faceName } from "./font-faces.js";
16
+ export { installNodeGlobal } from "./node-global.js";
17
+ export { faceName, registeredFaces } from "./font-faces.js";
17
18
  export { firstFamily } from "./inline-token.js";
18
19
  export { DIRECT_EVENTS, Engine, SyntheticEvent, claimHost, keepNativeView, declareNativeProps, fontsLoading, fontsRegistered, fontsSettled, markComponentHost, nativePlatform, onFontsRegistered, onFontsSettled, registerHoist, registerPlatformComponents, registerViewName, viewNameOf, } from "./engine.js";
19
20
  export { reportUnboundFormsInput } from "./forms-inputs.js";
package/dist/index.js.map CHANGED
@@ -1 +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,cAAc,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzE,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EACL,aAAa,EACb,MAAM,EACN,cAAc,EACd,SAAS,EACT,cAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,0BAA0B,EAC1B,gBAAgB,EAGhB,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;AACzB,OAAO,EAAE,gBAAgB,EAAyB,MAAM,wBAAwB,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 docs/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 { ELEMENT_STYLES, ELEMENT_STYLES_CSS } from './element-styles.ts';\nexport { EngineIntersectionObserver, installDeferTriggers } from './defer-triggers.ts';\nexport { faceName } from './font-faces.ts';\nexport { firstFamily } from './inline-token.ts';\nexport {\n DIRECT_EVENTS,\n Engine,\n SyntheticEvent,\n claimHost,\n keepNativeView,\n declareNativeProps,\n fontsLoading,\n fontsRegistered,\n fontsSettled,\n markComponentHost,\n nativePlatform,\n onFontsRegistered,\n onFontsSettled,\n registerHoist,\n registerPlatformComponents,\n registerViewName,\n type PlatformViewName,\n type ViewNameOptions,\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';\nexport { backgroundLayers, type BackgroundLayers } from './background-layers.ts';\n"]}
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,cAAc,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzE,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EACL,aAAa,EACb,MAAM,EACN,cAAc,EACd,SAAS,EACT,cAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,0BAA0B,EAC1B,gBAAgB,EAIhB,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;AACzB,OAAO,EAAE,gBAAgB,EAAyB,MAAM,wBAAwB,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 docs/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 { ELEMENT_STYLES, ELEMENT_STYLES_CSS } from './element-styles.ts';\nexport { EngineIntersectionObserver, installDeferTriggers } from './defer-triggers.ts';\nexport { installNodeGlobal } from './node-global.ts';\nexport { faceName, registeredFaces } from './font-faces.ts';\nexport { firstFamily } from './inline-token.ts';\nexport {\n DIRECT_EVENTS,\n Engine,\n SyntheticEvent,\n claimHost,\n keepNativeView,\n declareNativeProps,\n fontsLoading,\n fontsRegistered,\n fontsSettled,\n markComponentHost,\n nativePlatform,\n onFontsRegistered,\n onFontsSettled,\n registerHoist,\n registerPlatformComponents,\n registerViewName,\n type PlatformViewName,\n type ViewNameOptions,\n type PostScriptNames,\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';\nexport { backgroundLayers, type BackgroundLayers } from './background-layers.ts';\n"]}
@@ -323,6 +323,8 @@ function mixColour(cursor) {
323
323
  return { color: (match(cursor, WORD) ?? fail())[0] };
324
324
  if (RELATIVE[fn] && takeWord(cursor, 'from'))
325
325
  return relativeExpression(cursor, RELATIVE[fn]);
326
+ if (fn === 'color' && takeWord(cursor, 'from'))
327
+ return srgbExpression(cursor);
326
328
  cursor.at = closing(cursor);
327
329
  return functionColour(cursor.text.slice(start, cursor.at));
328
330
  }
@@ -423,16 +425,44 @@ function relativeExpression(cursor, { space, keywords, full }) {
423
425
  return { relative: { space, from, channels, ...(alpha === undefined ? {} : { alpha }) } };
424
426
  }
425
427
  /**
426
- * One channel: a `calc()` of numbers and keywords, a keyword, a number, a percentage of the
427
- * channel's range, or an angle for a hue.
428
+ * The rest of `color(from <colour> srgb <channel> <channel> <channel> [/ <alpha>])` once `from` is
429
+ * read: what Highcharts writes for the colour a label stands out in on a colour it cannot read.
430
+ * Its `r g b` run from 0 to 1, as the channels it makes do: each is the same channel of `rgb()`
431
+ * read as a share of 255.
432
+ */
433
+ function srgbExpression(cursor) {
434
+ const from = mixColour(cursor);
435
+ match(cursor, SPACE);
436
+ if (!takeWord(cursor, 'srgb'))
437
+ fail();
438
+ const names = ['r', 'g', 'b', 'alpha'];
439
+ const share = (channel) => typeof channel === 'string'
440
+ ? channel === 'alpha'
441
+ ? channel
442
+ : ['/', channel, 255]
443
+ : typeof channel === 'number'
444
+ ? channel
445
+ : [channel[0], share(channel[1]), share(channel[2])];
446
+ const channels = [0, 1, 2].map(() => {
447
+ match(cursor, SPACE);
448
+ return ['*', share(relativeChannel(cursor, names, 1)), 255];
449
+ });
450
+ const alpha = take(cursor, '/') ? share(relativeChannel(cursor, names, 1)) : undefined;
451
+ expect(cursor, ')');
452
+ return {
453
+ relative: { space: 'srgb', from, channels, ...(alpha === undefined ? {} : { alpha }) },
454
+ };
455
+ }
456
+ /** The functions a channel is worked out with: arithmetic, and the least, the most or between. */
457
+ const CHANNEL_MATH = new Set(['calc', 'min', 'max', 'clamp']);
458
+ /**
459
+ * One channel: a `calc()`, `min()`, `max()` or `clamp()` of numbers and keywords, a keyword, a
460
+ * number, a percentage of the channel's range, or an angle for a hue.
428
461
  */
429
462
  function relativeChannel(cursor, keywords, full) {
430
463
  const fn = match(cursor, FUNCTION)?.[1]?.toLowerCase();
431
- if (fn) {
432
- const value = fn === 'calc' ? channelSum(cursor, keywords) : fail();
433
- expect(cursor, ')');
434
- return value;
435
- }
464
+ if (fn)
465
+ return channelMath(cursor, fn, keywords);
436
466
  const found = match(cursor, LITERAL);
437
467
  if (!found)
438
468
  return channelOperand(cursor, keywords);
@@ -462,12 +492,31 @@ function channelProduct(cursor, keywords) {
462
492
  }
463
493
  return value;
464
494
  }
495
+ /** The rest of a `calc()`, `min()`, `max()` or `clamp()` of channels, once its name is read. */
496
+ function channelMath(cursor, fn, keywords) {
497
+ if (!CHANNEL_MATH.has(fn))
498
+ fail();
499
+ const sides = [channelSum(cursor, keywords)];
500
+ while (fn !== 'calc' && take(cursor, ','))
501
+ sides.push(channelSum(cursor, keywords));
502
+ expect(cursor, ')');
503
+ if (fn === 'clamp') {
504
+ if (sides.length !== 3)
505
+ fail();
506
+ const [least, value, most] = sides;
507
+ return ['max', least, ['min', value, most]];
508
+ }
509
+ return sides.reduce((a, b) => [fn, a, b]);
510
+ }
465
511
  function channelFactor(cursor, keywords) {
466
512
  if (take(cursor, '(')) {
467
513
  const value = channelSum(cursor, keywords);
468
514
  expect(cursor, ')');
469
515
  return value;
470
516
  }
517
+ const fn = match(cursor, FUNCTION)?.[1]?.toLowerCase();
518
+ if (fn)
519
+ return channelMath(cursor, fn, keywords);
471
520
  const found = match(cursor, LITERAL);
472
521
  // A percentage or an angle stands alone: inside arithmetic the keywords are numbers.
473
522
  if (found)