@ng-native/fabric 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/css.d.ts +64 -13
- package/dist/css.js +209 -46
- package/dist/css.js.map +1 -1
- package/dist/engine.d.ts +56 -3
- package/dist/engine.js +144 -9
- package/dist/engine.js.map +1 -1
- package/dist/font-faces.d.ts +42 -0
- package/dist/font-faces.js +112 -0
- package/dist/font-faces.js.map +1 -0
- package/dist/forms-inputs.js +1 -1
- package/dist/forms-inputs.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/inline-token.d.ts +3 -2
- package/dist/inline-token.js +73 -1
- package/dist/inline-token.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Font matching, which native does not do.
|
|
3
|
+
*
|
|
4
|
+
* A device finds a family by name and nothing else, so each `@font-face` is registered under a
|
|
5
|
+
* name of its own (`faceName`), and a text asking for a declared family with a weight or a style
|
|
6
|
+
* is pointed at the face CSS would pick for it. That happens on the resolved style, after the
|
|
7
|
+
* cascade, because a family and a weight often come from different rules or from an ancestor.
|
|
8
|
+
*/
|
|
9
|
+
import type { StyleSheet } from './css.ts';
|
|
10
|
+
type FontFace = NonNullable<StyleSheet['fonts']>[number];
|
|
11
|
+
/**
|
|
12
|
+
* When a face was picked, whether to drop the `fontWeight` and `fontStyle` it was picked for from
|
|
13
|
+
* what is committed. The face already draws them, and kept, the platform may embolden or slant it
|
|
14
|
+
* again on top.
|
|
15
|
+
*/
|
|
16
|
+
export declare const DROP_MATCHED_WEIGHT_AND_STYLE = true;
|
|
17
|
+
/**
|
|
18
|
+
* The name a face is registered under: the family, then its weight and its style where it
|
|
19
|
+
* declares them. `Inter`, `Inter-600`, `Inter-italic`, `Inter-600-italic`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function faceName(face: Pick<FontFace, 'family' | 'weight' | 'style'>): string;
|
|
22
|
+
/**
|
|
23
|
+
* The face CSS's matching picks among one family's, for a wanted weight and style.
|
|
24
|
+
*
|
|
25
|
+
* Style first, then weight, as in CSS Fonts 4 section 5.2. For a weight from 400 to 500, the
|
|
26
|
+
* weights up to 500 are tried first, then lighter ones, then heavier; under 400, lighter then
|
|
27
|
+
* heavier; over 500, heavier then lighter. Each direction nearest first.
|
|
28
|
+
*/
|
|
29
|
+
export declare function matchFace(faces: readonly FontFace[], weight: number, style: string): FontFace | undefined;
|
|
30
|
+
/** Every face the engine has seen a sheet declare, by family. */
|
|
31
|
+
export declare class FontFaces {
|
|
32
|
+
private readonly byFamily;
|
|
33
|
+
/** Take faces in; whether any was new, which changes what text already matched may match. */
|
|
34
|
+
add(faces: readonly FontFace[]): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Point a resolved style's family at the face its weight and style ask for. A family nothing
|
|
37
|
+
* declared is left alone, and so is the family of one whose match is the face registered under
|
|
38
|
+
* the family itself.
|
|
39
|
+
*/
|
|
40
|
+
apply(style: Record<string, unknown>): void;
|
|
41
|
+
}
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* When a face was picked, whether to drop the `fontWeight` and `fontStyle` it was picked for from
|
|
3
|
+
* what is committed. The face already draws them, and kept, the platform may embolden or slant it
|
|
4
|
+
* again on top.
|
|
5
|
+
*/
|
|
6
|
+
export const DROP_MATCHED_WEIGHT_AND_STYLE = true;
|
|
7
|
+
/**
|
|
8
|
+
* The name a face is registered under: the family, then its weight and its style where it
|
|
9
|
+
* declares them. `Inter`, `Inter-600`, `Inter-italic`, `Inter-600-italic`.
|
|
10
|
+
*/
|
|
11
|
+
export function faceName(face) {
|
|
12
|
+
let name = face.family;
|
|
13
|
+
if (face.weight !== undefined)
|
|
14
|
+
name += `-${face.weight}`;
|
|
15
|
+
if (face.style)
|
|
16
|
+
name += `-${face.style}`;
|
|
17
|
+
return name;
|
|
18
|
+
}
|
|
19
|
+
/** CSS's fallback order for a style: the closest slant first, upright last. */
|
|
20
|
+
const STYLE_ORDER = {
|
|
21
|
+
italic: ['italic', 'oblique', 'normal'],
|
|
22
|
+
oblique: ['oblique', 'italic', 'normal'],
|
|
23
|
+
normal: ['normal', 'oblique', 'italic'],
|
|
24
|
+
};
|
|
25
|
+
/** A `fontWeight` as a number: `normal` and unset are 400, `bold` 700. NaN for anything else. */
|
|
26
|
+
function weightOf(value) {
|
|
27
|
+
if (value === undefined || value === 'normal')
|
|
28
|
+
return 400;
|
|
29
|
+
if (value === 'bold')
|
|
30
|
+
return 700;
|
|
31
|
+
return Number(value);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The face CSS's matching picks among one family's, for a wanted weight and style.
|
|
35
|
+
*
|
|
36
|
+
* Style first, then weight, as in CSS Fonts 4 section 5.2. For a weight from 400 to 500, the
|
|
37
|
+
* weights up to 500 are tried first, then lighter ones, then heavier; under 400, lighter then
|
|
38
|
+
* heavier; over 500, heavier then lighter. Each direction nearest first.
|
|
39
|
+
*/
|
|
40
|
+
export function matchFace(faces, weight, style) {
|
|
41
|
+
const slant = (face) => face.style ?? 'normal';
|
|
42
|
+
const order = STYLE_ORDER[style] ?? STYLE_ORDER['normal'];
|
|
43
|
+
const wantedStyle = order.find((one) => faces.some((face) => slant(face) === one));
|
|
44
|
+
const candidates = faces.filter((face) => slant(face) === wantedStyle);
|
|
45
|
+
const weightOfFace = (face) => face.weight ?? 400;
|
|
46
|
+
const heavier = (from, to) => candidates
|
|
47
|
+
.filter((face) => weightOfFace(face) >= from && weightOfFace(face) <= to)
|
|
48
|
+
.sort((a, b) => weightOfFace(a) - weightOfFace(b))[0];
|
|
49
|
+
const lighter = (upTo) => candidates
|
|
50
|
+
.filter((face) => weightOfFace(face) <= upTo)
|
|
51
|
+
.sort((a, b) => weightOfFace(b) - weightOfFace(a))[0];
|
|
52
|
+
if (weight >= 400 && weight <= 500) {
|
|
53
|
+
return heavier(weight, 500) ?? lighter(weight) ?? heavier(500, Infinity);
|
|
54
|
+
}
|
|
55
|
+
if (weight < 400)
|
|
56
|
+
return lighter(weight) ?? heavier(weight, Infinity);
|
|
57
|
+
return heavier(weight, Infinity) ?? lighter(weight);
|
|
58
|
+
}
|
|
59
|
+
/** A family as CSS compares it: without regard to case. */
|
|
60
|
+
const familyKey = (family) => family.toLowerCase();
|
|
61
|
+
/** Every face the engine has seen a sheet declare, by family. */
|
|
62
|
+
export class FontFaces {
|
|
63
|
+
byFamily = new Map();
|
|
64
|
+
/** Take faces in; whether any was new, which changes what text already matched may match. */
|
|
65
|
+
add(faces) {
|
|
66
|
+
let added = false;
|
|
67
|
+
for (const face of faces) {
|
|
68
|
+
const known = this.byFamily.get(familyKey(face.family));
|
|
69
|
+
if (known?.some((one) => faceName(one) === faceName(face)))
|
|
70
|
+
continue;
|
|
71
|
+
if (known)
|
|
72
|
+
known.push(face);
|
|
73
|
+
else
|
|
74
|
+
this.byFamily.set(familyKey(face.family), [face]);
|
|
75
|
+
added = true;
|
|
76
|
+
}
|
|
77
|
+
return added;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Point a resolved style's family at the face its weight and style ask for. A family nothing
|
|
81
|
+
* declared is left alone, and so is the family of one whose match is the face registered under
|
|
82
|
+
* the family itself.
|
|
83
|
+
*/
|
|
84
|
+
apply(style) {
|
|
85
|
+
if (this.byFamily.size === 0)
|
|
86
|
+
return;
|
|
87
|
+
const family = style['fontFamily'];
|
|
88
|
+
if (typeof family !== 'string')
|
|
89
|
+
return;
|
|
90
|
+
const faces = this.byFamily.get(familyKey(family));
|
|
91
|
+
if (!faces)
|
|
92
|
+
return;
|
|
93
|
+
const weight = weightOf(style['fontWeight']);
|
|
94
|
+
if (Number.isNaN(weight))
|
|
95
|
+
return;
|
|
96
|
+
const face = matchFace(faces, weight, String(style['fontStyle'] ?? 'normal'));
|
|
97
|
+
if (!face)
|
|
98
|
+
return;
|
|
99
|
+
const name = faceName(face);
|
|
100
|
+
if (name !== family)
|
|
101
|
+
style['fontFamily'] = name;
|
|
102
|
+
if (!DROP_MATCHED_WEIGHT_AND_STYLE)
|
|
103
|
+
return;
|
|
104
|
+
// The style goes whichever face matched: a slanted face draws its slant, and beside an
|
|
105
|
+
// upright one Android draws its system font slanted in place of the face, which iOS draws
|
|
106
|
+
// upright anyway.
|
|
107
|
+
delete style['fontStyle'];
|
|
108
|
+
if (face.weight !== undefined)
|
|
109
|
+
delete style['fontWeight'];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=font-faces.js.map
|
|
@@ -0,0 +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`.\n */\nexport function faceName(face: Pick<FontFace, 'family' | 'weight' | 'style'>): string {\n let name = face.family;\n if (face.weight !== undefined) name += `-${face.weight}`;\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 const weightOfFace = (face: FontFace) => 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 /** 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 if (face.weight !== undefined) delete style['fontWeight'];\n }\n}\n"]}
|
package/dist/forms-inputs.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* and the report both `Renderer2` adapters make when one reaches them unclaimed.
|
|
4
4
|
*
|
|
5
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
|
|
6
|
+
* package - everything else here stays framework-agnostic, per `docs/ARCHITECTURE.md`. It lives here
|
|
7
7
|
* anyway, rather than duplicated in `@ng-native/platform` and `@ng-native/web`, because both of
|
|
8
8
|
* their `Renderer2`s hit exactly the same failure for exactly the same reason and neither imports
|
|
9
9
|
* the other: a binding reaches `setProperty` only when no directive on the element took it, and
|
package/dist/forms-inputs.js.map
CHANGED
|
@@ -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 `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,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"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The engine's public surface.
|
|
3
3
|
*
|
|
4
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
|
|
5
|
+
* framework-agnostic (see docs/ARCHITECTURE.md), which is what keeps the commit logic testable without Angular in
|
|
6
6
|
* the loop and a second adapter possible later.
|
|
7
7
|
*
|
|
8
8
|
* The CSS runtime lives here rather than in a package of its own because the cascade is driven
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
*/
|
|
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 { EngineIntersectionObserver, installDeferTriggers } from './defer-triggers.ts';
|
|
15
|
-
export {
|
|
15
|
+
export { faceName } from './font-faces.ts';
|
|
16
|
+
export { Engine, SyntheticEvent, claimHost, declareNativeProps, fontsRegistered, markComponentHost, nativePlatform, onFontsRegistered, 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
17
|
export { reportUnboundFormsInput, type NamedHostNode } from './forms-inputs.ts';
|
|
17
18
|
export { HostEngine, type HostNode, type Settling } from './host.ts';
|
|
18
19
|
export { getFabricUIManager } from './fabric.ts';
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The engine's public surface.
|
|
3
3
|
*
|
|
4
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
|
|
5
|
+
* framework-agnostic (see docs/ARCHITECTURE.md), which is what keeps the commit logic testable without Angular in
|
|
6
6
|
* the loop and a second adapter possible later.
|
|
7
7
|
*
|
|
8
8
|
* The CSS runtime lives here rather than in a package of its own because the cascade is driven
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export { StyleResolver, matches, styleSheetOf, } from "./css.js";
|
|
14
14
|
export { EngineIntersectionObserver, installDeferTriggers } from "./defer-triggers.js";
|
|
15
|
-
export {
|
|
15
|
+
export { faceName } from "./font-faces.js";
|
|
16
|
+
export { Engine, SyntheticEvent, claimHost, declareNativeProps, fontsRegistered, markComponentHost, nativePlatform, onFontsRegistered, registerHoist, registerPlatformComponents, registerViewName, viewNameOf, } from "./engine.js";
|
|
16
17
|
export { reportUnboundFormsInput } from "./forms-inputs.js";
|
|
17
18
|
export { HostEngine } from "./host.js";
|
|
18
19
|
export { getFabricUIManager } from "./fabric.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,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"]}
|
|
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,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EACL,MAAM,EACN,cAAc,EACd,SAAS,EACT,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,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 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 { EngineIntersectionObserver, installDeferTriggers } from './defer-triggers.ts';\nexport { faceName } from './font-faces.ts';\nexport {\n Engine,\n SyntheticEvent,\n claimHost,\n declareNativeProps,\n fontsRegistered,\n markComponentHost,\n nativePlatform,\n onFontsRegistered,\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"]}
|
package/dist/inline-token.d.ts
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* A stylesheet's custom properties are converted when the app is built, where there is a CSS
|
|
6
6
|
* parser (`@ng-native/metro/css/values.cjs`). A bound one only exists here, where there is not,
|
|
7
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,
|
|
9
|
-
*
|
|
8
|
+
* number, a colour as React Native writes one, a colour's three channels, a word, and another
|
|
9
|
+
* token, `var(--brand)`. Anything else is kept as a word, which a use site that wants a length or
|
|
10
|
+
* a colour ignores, as it ignores an undefined token.
|
|
10
11
|
*/
|
|
11
12
|
import type { TokenValue } from './css.ts';
|
|
12
13
|
export declare function tokenFromValue(value: unknown): TokenValue | undefined;
|
package/dist/inline-token.js
CHANGED
|
@@ -5,6 +5,11 @@ const NUMBER = /^-?\d*\.?\d+$/;
|
|
|
5
5
|
/** `#fff`, `rgb()`, `rgba()`, `hsl()`, `hsla()` and `hwb()`: what React Native's `processColor` reads. */
|
|
6
6
|
const COLOR_FUNCTION = /^(#[\da-f]{3,8}|(rgba?|hsla?|hwb)\(.*\))$/i;
|
|
7
7
|
const WORD = /^-?[a-z][\w-]*$/i;
|
|
8
|
+
/** One colour channel: a number, a percentage, or a hue with its unit. */
|
|
9
|
+
const CHANNEL = /^(-?(?:\d+\.?\d*|\.\d+))(%|deg|grad|rad|turn)?$/;
|
|
10
|
+
const HUE_UNITS = { deg: 1, grad: 0.9, rad: 180 / Math.PI, turn: 360 };
|
|
11
|
+
/** `var(--name)` or `var(--name, <fallback>)`, the whole value. */
|
|
12
|
+
const VAR = /^var\(\s*(--[\w-]+)\s*(?:,([\s\S]*))?\)$/i;
|
|
8
13
|
const WEIGHTS = { normal: '400', bold: '700' };
|
|
9
14
|
/**
|
|
10
15
|
* CSS takes a bare 0 wherever it takes a length, and no other bare number. A number from 1 to 1000
|
|
@@ -30,6 +35,70 @@ function fromWord(text) {
|
|
|
30
35
|
...(weight ? { weight } : {}),
|
|
31
36
|
};
|
|
32
37
|
}
|
|
38
|
+
const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
|
|
39
|
+
/**
|
|
40
|
+
* `13 110 253`, `13, 110, 253`, `100% 0% 0%` or `0 100% 50%`, in the forms `rgb()` and `hsl()` read
|
|
41
|
+
* channels in, by the same rules the build-time conversion uses (`channelForms` in values.cjs).
|
|
42
|
+
*/
|
|
43
|
+
function fromChannels(text) {
|
|
44
|
+
const commas = text.split(',');
|
|
45
|
+
const spaced = commas.length === 1;
|
|
46
|
+
const words = spaced ? text.split(/\s+/) : commas.map((word) => word.trim());
|
|
47
|
+
const parsed = words.map((word) => CHANNEL.exec(word));
|
|
48
|
+
if (parsed.length !== 3 || parsed.some((match) => !match))
|
|
49
|
+
return undefined;
|
|
50
|
+
const values = parsed.map((match) => ({ n: Number(match[1]), unit: match[2] ?? '' }));
|
|
51
|
+
const channels = rgbChannels(values);
|
|
52
|
+
const hslChannels = hslOf(values, spaced);
|
|
53
|
+
if (!channels && !hslChannels)
|
|
54
|
+
return undefined;
|
|
55
|
+
return { ...(channels ? { channels } : {}), ...(hslChannels ? { hslChannels } : {}) };
|
|
56
|
+
}
|
|
57
|
+
/** As `rgb()` reads them: three numbers, or three percentages of 255. */
|
|
58
|
+
function rgbChannels(values) {
|
|
59
|
+
const scale = values.every((v) => v.unit === '')
|
|
60
|
+
? 1
|
|
61
|
+
: values.every((v) => v.unit === '%')
|
|
62
|
+
? 255 / 100
|
|
63
|
+
: 0;
|
|
64
|
+
if (!scale)
|
|
65
|
+
return undefined;
|
|
66
|
+
return values.map((v) => Math.round(clamp(v.n * scale, 0, 255) * 1000) / 1000);
|
|
67
|
+
}
|
|
68
|
+
/** As `hsl()` reads them: a hue, then two percentages, which the space syntax may write bare. */
|
|
69
|
+
function hslOf([hue, ...rest], spaced) {
|
|
70
|
+
const degrees = hue.n * (hue.unit === '' ? 1 : (HUE_UNITS[hue.unit] ?? NaN));
|
|
71
|
+
const fractions = rest.map((v) => v.unit === '%' || (spaced && v.unit === '') ? clamp(v.n / 100, 0, 1) : NaN);
|
|
72
|
+
const hsl = [degrees, ...fractions];
|
|
73
|
+
return hsl.every(Number.isFinite) ? hsl : undefined;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Another token, kept as an alias and resolved where it is set, as a stylesheet's `--x: var(--y)`
|
|
77
|
+
* is. Undefined when the parentheses do not close where the value ends: `var(--a, 1px) var(--b)`.
|
|
78
|
+
*/
|
|
79
|
+
function fromVar(text) {
|
|
80
|
+
const match = VAR.exec(text);
|
|
81
|
+
if (!match)
|
|
82
|
+
return undefined;
|
|
83
|
+
const alias = match[1];
|
|
84
|
+
const rest = match[2];
|
|
85
|
+
if (rest === undefined)
|
|
86
|
+
return { alias };
|
|
87
|
+
if (!balanced(rest))
|
|
88
|
+
return undefined;
|
|
89
|
+
const fallback = tokenFromValue(rest);
|
|
90
|
+
return fallback ? { alias, fallback } : { alias };
|
|
91
|
+
}
|
|
92
|
+
function balanced(text) {
|
|
93
|
+
let depth = 0;
|
|
94
|
+
for (const char of text) {
|
|
95
|
+
if (char === '(')
|
|
96
|
+
depth++;
|
|
97
|
+
else if (char === ')' && --depth < 0)
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return depth === 0;
|
|
101
|
+
}
|
|
33
102
|
export function tokenFromValue(value) {
|
|
34
103
|
if (typeof value === 'number')
|
|
35
104
|
return fromNumber(value);
|
|
@@ -47,6 +116,9 @@ export function tokenFromValue(value) {
|
|
|
47
116
|
return fromNumber(Number(text));
|
|
48
117
|
if (COLOR_FUNCTION.test(text))
|
|
49
118
|
return { color: text };
|
|
50
|
-
|
|
119
|
+
const reference = fromVar(text);
|
|
120
|
+
if (reference)
|
|
121
|
+
return reference;
|
|
122
|
+
return fromChannels(text) ?? (WORD.test(text) ? fromWord(text) : { keyword: text });
|
|
51
123
|
}
|
|
52
124
|
//# sourceMappingURL=inline-token.js.map
|
package/dist/inline-token.js.map
CHANGED
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"file":"inline-token.js","sourceRoot":"","sources":["../src/inline-token.ts"],"names":[],"mappings":"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","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, a colour's three channels, a word, and another\n * token, `var(--brand)`. Anything else is kept as a word, which a use site that wants a length or\n * 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;\n/** One colour channel: a number, a percentage, or a hue with its unit. */\nconst CHANNEL = /^(-?(?:\\d+\\.?\\d*|\\.\\d+))(%|deg|grad|rad|turn)?$/;\nconst HUE_UNITS: Record<string, number> = { deg: 1, grad: 0.9, rad: 180 / Math.PI, turn: 360 };\n/** `var(--name)` or `var(--name, <fallback>)`, the whole value. */\nconst VAR = /^var\\(\\s*(--[\\w-]+)\\s*(?:,([\\s\\S]*))?\\)$/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\nconst clamp = (value: number, low: number, high: number) => Math.min(high, Math.max(low, value));\n\n/**\n * `13 110 253`, `13, 110, 253`, `100% 0% 0%` or `0 100% 50%`, in the forms `rgb()` and `hsl()` read\n * channels in, by the same rules the build-time conversion uses (`channelForms` in values.cjs).\n */\nfunction fromChannels(text: string): TokenValue | undefined {\n const commas = text.split(',');\n const spaced = commas.length === 1;\n const words = spaced ? text.split(/\\s+/) : commas.map((word) => word.trim());\n const parsed = words.map((word) => CHANNEL.exec(word));\n if (parsed.length !== 3 || parsed.some((match) => !match)) return undefined;\n const values = parsed.map((match) => ({ n: Number(match![1]), unit: match![2] ?? '' }));\n const channels = rgbChannels(values);\n const hslChannels = hslOf(values, spaced);\n if (!channels && !hslChannels) return undefined;\n return { ...(channels ? { channels } : {}), ...(hslChannels ? { hslChannels } : {}) };\n}\n\ninterface Channel {\n readonly n: number;\n readonly unit: string;\n}\n\n/** As `rgb()` reads them: three numbers, or three percentages of 255. */\nfunction rgbChannels(values: readonly Channel[]): number[] | undefined {\n const scale = values.every((v) => v.unit === '')\n ? 1\n : values.every((v) => v.unit === '%')\n ? 255 / 100\n : 0;\n if (!scale) return undefined;\n return values.map((v) => Math.round(clamp(v.n * scale, 0, 255) * 1000) / 1000);\n}\n\n/** As `hsl()` reads them: a hue, then two percentages, which the space syntax may write bare. */\nfunction hslOf([hue, ...rest]: readonly Channel[], spaced: boolean): number[] | undefined {\n const degrees = hue!.n * (hue!.unit === '' ? 1 : (HUE_UNITS[hue!.unit] ?? NaN));\n const fractions = rest.map((v) =>\n v.unit === '%' || (spaced && v.unit === '') ? clamp(v.n / 100, 0, 1) : NaN,\n );\n const hsl = [degrees, ...fractions];\n return hsl.every(Number.isFinite) ? hsl : undefined;\n}\n\n/**\n * Another token, kept as an alias and resolved where it is set, as a stylesheet's `--x: var(--y)`\n * is. Undefined when the parentheses do not close where the value ends: `var(--a, 1px) var(--b)`.\n */\nfunction fromVar(text: string): TokenValue | undefined {\n const match = VAR.exec(text);\n if (!match) return undefined;\n const alias = match[1]!;\n const rest = match[2];\n if (rest === undefined) return { alias };\n if (!balanced(rest)) return undefined;\n const fallback = tokenFromValue(rest);\n return fallback ? { alias, fallback } : { alias };\n}\n\nfunction balanced(text: string): boolean {\n let depth = 0;\n for (const char of text) {\n if (char === '(') depth++;\n else if (char === ')' && --depth < 0) return false;\n }\n return depth === 0;\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 const reference = fromVar(text);\n if (reference) return reference;\n return fromChannels(text) ?? (WORD.test(text) ? fromWord(text) : { keyword: text });\n}\n"]}
|
package/package.json
CHANGED