@ng-native/fabric 0.1.2 → 0.2.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.
@@ -1,10 +1,17 @@
1
+ import { derivedToken } from "./inline-derived-token.js";
1
2
  import { isNamedColor } from "./transition.js";
2
3
  const PX = /^(-?\d*\.?\d+)px$/;
3
4
  const PERCENT = /^-?\d*\.?\d+%$/;
4
5
  const NUMBER = /^-?\d*\.?\d+$/;
6
+ const ANGLE = /^(-?\d*\.?\d+)(deg|grad|rad|turn)$/i;
5
7
  /** `#fff`, `rgb()`, `rgba()`, `hsl()`, `hsla()` and `hwb()`: what React Native's `processColor` reads. */
6
8
  const COLOR_FUNCTION = /^(#[\da-f]{3,8}|(rgba?|hsla?|hwb)\(.*\))$/i;
7
9
  const WORD = /^-?[a-z][\w-]*$/i;
10
+ /** One colour channel: a number, a percentage, or a hue with its unit. */
11
+ const CHANNEL = /^(-?(?:\d+\.?\d*|\.\d+))(%|deg|grad|rad|turn)?$/;
12
+ const HUE_UNITS = { deg: 1, grad: 0.9, rad: 180 / Math.PI, turn: 360 };
13
+ /** `var(--name)` or `var(--name, <fallback>)`, the whole value. */
14
+ const VAR = /^var\(\s*(--[\w-]+)\s*(?:,([\s\S]*))?\)$/i;
8
15
  const WEIGHTS = { normal: '400', bold: '700' };
9
16
  /**
10
17
  * CSS takes a bare 0 wherever it takes a length, and no other bare number. A number from 1 to 1000
@@ -30,6 +37,78 @@ function fromWord(text) {
30
37
  ...(weight ? { weight } : {}),
31
38
  };
32
39
  }
40
+ const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
41
+ /**
42
+ * `13 110 253`, `13, 110, 253`, `100% 0% 0%` or `0 100% 50%`, in the forms `rgb()` and `hsl()` read
43
+ * channels in, by the same rules the build-time conversion uses (`channelForms` in values.cjs).
44
+ */
45
+ function fromChannels(text) {
46
+ const commas = text.split(',');
47
+ const spaced = commas.length === 1;
48
+ const words = spaced ? text.split(/\s+/) : commas.map((word) => word.trim());
49
+ const parsed = words.map((word) => CHANNEL.exec(word));
50
+ if (parsed.length !== 3 || parsed.some((match) => !match))
51
+ return undefined;
52
+ const values = parsed.map((match) => ({ n: Number(match[1]), unit: match[2] ?? '' }));
53
+ const channels = rgbChannels(values);
54
+ const hslChannels = hslOf(values, spaced);
55
+ if (!channels && !hslChannels)
56
+ return undefined;
57
+ return { ...(channels ? { channels } : {}), ...(hslChannels ? { hslChannels } : {}) };
58
+ }
59
+ /** As `rgb()` reads them: three numbers, or three percentages of 255. */
60
+ function rgbChannels(values) {
61
+ const scale = values.every((v) => v.unit === '')
62
+ ? 1
63
+ : values.every((v) => v.unit === '%')
64
+ ? 255 / 100
65
+ : 0;
66
+ if (!scale)
67
+ return undefined;
68
+ return values.map((v) => Math.round(clamp(v.n * scale, 0, 255) * 1000) / 1000);
69
+ }
70
+ /** As `hsl()` reads them: a hue, then two percentages, which the space syntax may write bare. */
71
+ function hslOf([hue, ...rest], spaced) {
72
+ const degrees = hue.n * (hue.unit === '' ? 1 : (HUE_UNITS[hue.unit] ?? NaN));
73
+ const fractions = rest.map((v) => v.unit === '%' || (spaced && v.unit === '') ? clamp(v.n / 100, 0, 1) : NaN);
74
+ const hsl = [degrees, ...fractions];
75
+ return hsl.every(Number.isFinite) ? hsl : undefined;
76
+ }
77
+ /**
78
+ * Another token, kept as an alias and resolved where it is set, as a stylesheet's `--x: var(--y)`
79
+ * is. Undefined when the parentheses do not close where the value ends: `var(--a, 1px) var(--b)`.
80
+ */
81
+ function fromVar(text) {
82
+ const match = VAR.exec(text);
83
+ if (!match)
84
+ return undefined;
85
+ const alias = match[1];
86
+ const rest = match[2];
87
+ if (rest === undefined)
88
+ return { alias };
89
+ if (!balanced(rest))
90
+ return undefined;
91
+ const fallback = tokenFromValue(rest);
92
+ return fallback ? { alias, fallback } : { alias };
93
+ }
94
+ function balanced(text) {
95
+ let depth = 0;
96
+ for (const char of text) {
97
+ if (char === '(')
98
+ depth++;
99
+ else if (char === ')' && --depth < 0)
100
+ return false;
101
+ }
102
+ return depth === 0;
103
+ }
104
+ /**
105
+ * A value with a token in it: another token, or one made of others, worked out where it is set.
106
+ * Any other shape is a word no use site reads, as a colour function with a `var()` left in it is
107
+ * not a colour native can draw.
108
+ */
109
+ function withTokens(text) {
110
+ return fromVar(text) ?? derivedToken(text) ?? { keyword: text };
111
+ }
33
112
  export function tokenFromValue(value) {
34
113
  if (typeof value === 'number')
35
114
  return fromNumber(value);
@@ -41,12 +120,21 @@ export function tokenFromValue(value) {
41
120
  const px = PX.exec(text);
42
121
  if (px)
43
122
  return { length: Number(px[1]) };
123
+ // A percentage is a fraction as well, and an angle its degrees, as the build-time conversion
124
+ // reads them: an hsl() of tokens takes its saturation and its hue so.
44
125
  if (PERCENT.test(text))
45
- return { length: text };
126
+ return { length: text, number: Number(text.slice(0, -1)) / 100 };
127
+ const angle = ANGLE.exec(text);
128
+ if (angle) {
129
+ const [, value, unit] = angle;
130
+ return { angle: Number(value) * HUE_UNITS[unit.toLowerCase()], number: Number(value) };
131
+ }
46
132
  if (NUMBER.test(text))
47
133
  return fromNumber(Number(text));
134
+ if (text.includes('var('))
135
+ return withTokens(text);
48
136
  if (COLOR_FUNCTION.test(text))
49
137
  return { color: text };
50
- return WORD.test(text) ? fromWord(text) : { keyword: text };
138
+ return fromChannels(text) ?? (WORD.test(text) ? fromWord(text) : { keyword: text });
51
139
  }
52
140
  //# sourceMappingURL=inline-token.js.map
@@ -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, another token,\n * `var(--brand)`, and one made of others, `calc(var(--gap) * 2)` (see inline-derived-token.ts).\n * Anything else is kept as a word, which a use site that wants a length or a colour ignores, as\n * it ignores an undefined token.\n */\nimport type { TokenValue } from './css.ts';\nimport { derivedToken } from './inline-derived-token.ts';\nimport { isNamedColor } from './transition.ts';\n\nconst PX = /^(-?\\d*\\.?\\d+)px$/;\nconst PERCENT = /^-?\\d*\\.?\\d+%$/;\nconst NUMBER = /^-?\\d*\\.?\\d+$/;\nconst ANGLE = /^(-?\\d*\\.?\\d+)(deg|grad|rad|turn)$/i;\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\n/**\n * A value with a token in it: another token, or one made of others, worked out where it is set.\n * Any other shape is a word no use site reads, as a colour function with a `var()` left in it is\n * not a colour native can draw.\n */\nfunction withTokens(text: string): TokenValue {\n return fromVar(text) ?? derivedToken(text) ?? { keyword: text };\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 // A percentage is a fraction as well, and an angle its degrees, as the build-time conversion\n // reads them: an hsl() of tokens takes its saturation and its hue so.\n if (PERCENT.test(text)) return { length: text, number: Number(text.slice(0, -1)) / 100 };\n const angle = ANGLE.exec(text);\n if (angle) {\n const [, value, unit] = angle;\n return { angle: Number(value) * HUE_UNITS[unit!.toLowerCase()]!, number: Number(value) };\n }\n if (NUMBER.test(text)) return fromNumber(Number(text));\n if (text.includes('var(')) return withTokens(text);\n if (COLOR_FUNCTION.test(text)) return { color: text };\n return fromChannels(text) ?? (WORD.test(text) ? fromWord(text) : { keyword: text });\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-native/fabric",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Angular Native's engine: a retained view tree committed to React Native's Fabric, with events, the responder system and a CSS cascade.",
5
5
  "keywords": [
6
6
  "angular",