@animus-ui/properties 0.1.19 → 0.1.21

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/dist/index.d.ts CHANGED
@@ -1,3 +1,145 @@
1
- export { SHORTHAND_PROPERTIES } from './shorthands';
2
- export { isUnitlessProperty, UNITLESS_PROPERTIES } from './unitless';
1
+ //#region src/shorthands.d.ts
2
+ /**
3
+ * Covers only shorthands registered in the prop system, not all CSS shorthands.
4
+ */
5
+ export declare const SHORTHAND_PROPERTIES: readonly ['border', 'borderTop', 'borderBottom', 'borderLeft', 'borderRight', 'borderWidth', 'borderStyle', 'borderColor', 'background', 'flex', 'margin', 'padding', 'transition', 'gap', 'grid', 'gridArea', 'gridColumn', 'gridRow', 'gridTemplate', 'overflow'];
6
+ //#endregion
7
+ //#region src/unitless.d.ts
8
+ export declare const UNITLESS_PROPERTIES: Set<string>;
9
+ /** Accepts either spelling: `line-height` or `lineHeight`,
10
+ * `-webkit-line-clamp` or `WebkitLineClamp`. */
11
+ export declare function isUnitlessProperty(property: string): boolean;
12
+ //#endregion
13
+ //#region src/css-tokens.d.ts
14
+ /**
15
+ * CSS tokens as CSS Syntax Level 3 reads them, with their source offsets.
16
+ * Comments are trivia and make no token. Identifiers, function names,
17
+ * at-keywords, hashes and units are decoded, escapes included. A quoted
18
+ * string and an unquoted `url()` are each one opaque token, so nothing inside
19
+ * them reads as a name or a function.
20
+ */
21
+ type CssTokenType = 'whitespace' | 'ident' | 'function' | 'at-keyword' | 'hash' | 'string' | 'bad-string' | 'url' | 'bad-url' | 'number' | 'percentage' | 'dimension' | 'delim' | 'CDO' | 'CDC' | '(' | ')' | '[' | ']' | '{' | '}' | ',' | ':' | ';';
22
+ interface CssToken {
23
+ type: CssTokenType;
24
+ start: number;
25
+ end: number;
26
+ /** The decoded name of an ident, function, at-keyword or hash, the
27
+ * decoded unit of a dimension, or a delim's character; otherwise `''`. */
28
+ value: string;
29
+ /** A number, percentage or dimension's numeric value; otherwise `0`. */
30
+ number: number;
31
+ }
32
+ /** The identifier starting at `index`, decoded, or `undefined` when none
33
+ * starts there. */
34
+ export declare function identifierAt(css: string, index: number): {
35
+ name: string;
36
+ end: number;
37
+ } | undefined;
38
+ /** `name` decoded when it is one whole identifier, else as written. */
39
+ export declare function decodedIdentifier(name: string): string;
40
+ /** Every token of `css`, in order. */
41
+ export declare function tokenize(css: string): CssToken[];
42
+ /** A component value: a function, or a `()`, `[]` or `{}` block, with its
43
+ * contents, or a single token. Offsets span the whole value. */
44
+ type ComponentValue = {
45
+ kind: 'block';
46
+ /** The function token, or the `(`, `[` or `{` token. */
47
+ open: CssToken;
48
+ /** `undefined` when the block is never closed. */
49
+ close: CssToken | undefined;
50
+ children: ComponentValue[];
51
+ start: number;
52
+ end: number;
53
+ } | {
54
+ kind: 'token';
55
+ token: CssToken;
56
+ start: number;
57
+ end: number;
58
+ };
59
+ /**
60
+ * `tokens` as component values. Only the innermost open block's own closer
61
+ * closes it; any other closer stays a token, and a block left open closes at
62
+ * the end, as CSS reads them.
63
+ */
64
+ export declare function componentValues(tokens: readonly CssToken[]): ComponentValue[];
65
+ /** Whether `test` holds for a value at any depth. */
66
+ export declare function someValue(values: readonly ComponentValue[], test: (value: ComponentValue) => boolean): boolean;
67
+ /** A `var()` read: the decoded custom-property name, the call's offsets,
68
+ * and its fallback's: after the comma, the first value's start, and before
69
+ * the closing parenthesis. */
70
+ interface VariableRead {
71
+ name: string;
72
+ start: number;
73
+ end: number;
74
+ fallback: {
75
+ start: number;
76
+ first: number;
77
+ end: number;
78
+ } | undefined;
79
+ }
80
+ /** Whether `value` is whitespace. */
81
+ export declare const isSpace: (value: ComponentValue | undefined) => boolean;
82
+ /**
83
+ * Every `var()` call in `values` at any depth, fallbacks included, outer
84
+ * calls first: a function whose decoded name is `var` in any case, and whose
85
+ * first argument is a custom-property name.
86
+ */
87
+ export declare function variableReads(values: readonly ComponentValue[]): VariableRead[];
88
+ //#endregion
89
+ //#region src/registration-values.d.ts
90
+ /**
91
+ * Reads a custom-property registration's initial value as CSS component
92
+ * values: what it substitutes, which units make it depend on context, and
93
+ * what an all-absolute math function computes to. Tokens come from the shared
94
+ * CSS reader, so comments are trivia, quoted strings and `url()` stay text,
95
+ * and escaped names are decoded.
96
+ */
97
+ /** Whether `value` substitutes another value (`var()`, `env()`, `attr()`),
98
+ * which no registration's initial value may. */
99
+ export declare function substitutesValue(value: string): boolean;
100
+ /** Whether `value` uses a font- or container-relative unit, inside math
101
+ * functions too, which makes a typed initial value depend on context. */
102
+ export declare function dependsOnContext(value: string): boolean;
103
+ /** What an all-absolute math expression computes to. */
104
+ interface Computed {
105
+ type: 'number' | 'length' | 'angle' | 'time' | 'resolution';
106
+ value: number;
107
+ }
108
+ /** What a math initial value folds to. */
109
+ type FoldedInitialValue =
110
+ /** The computed value the syntax accepts, as CSS text such as `1`, `16px`
111
+ * or `90deg`. */
112
+ {
113
+ kind: 'folded';
114
+ text: string;
115
+ } |
116
+ /** A computed value no syntax alternative accepts. */
117
+ {
118
+ kind: 'rejected';
119
+ text: string;
120
+ type: Computed['type'];
121
+ } |
122
+ /** Math that breaks CSS calculation syntax, which browsers ignore. */
123
+ {
124
+ kind: 'invalid';
125
+ reason: string;
126
+ } |
127
+ /** An infinite or NaN result: browsers clamp it, and it has no plain
128
+ * value to write. */
129
+ {
130
+ kind: 'not-finite';
131
+ };
132
+ /**
133
+ * The computed value of an initial value that is one math function (`calc()`,
134
+ * `min()`, `max()`, `clamp()`) over numbers and absolute units, such as
135
+ * `calc(1px / 1px)` → `1`. The browser computes the same value, and CSS
136
+ * tooling that cannot parse the function in that position reads the result.
137
+ * The syntax's alternatives are tried in their declared order, and an
138
+ * `<integer>` one rounds a number result to the nearest integer, halves
139
+ * toward positive infinity, as CSS does. `undefined` for anything else, and
140
+ * for the universal syntax, whose initial value is kept as written.
141
+ */
142
+ export declare function foldInitialValue(syntax: string, initialValue: string): FoldedInitialValue | undefined;
143
+ //#endregion
144
+ export type { ComponentValue, CssToken, FoldedInitialValue, VariableRead };
3
145
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/shorthands.ts","../src/unitless.ts","../src/css-tokens.ts","../src/registration-values.ts"],"mappings":";;;;qBAGa;;;qBCHA,qBAAmB;;;wBAgHhB,mBAAmB;;;;;;;;;;KCxGvB;UA0BK;EACf,MAAM;EACN;EACA;;;EAGA;;EAEA;;;;wBAiFc,aACd,aACA;EACG;EAAc;;;wBAOH,kBAAkB;;wBAyFlB,SAAS,cAAc;;;KA0E3B;EAEN;;EAEA,MAAM;;EAEN,OAAO;EACP,UAAU;EACV;EACA;;EAEA;EAAe,OAAO;EAAU;EAAe;;;;;;;wBAgBrC,gBAAgB,iBAAiB,aAAa;;wBAoC9C,UACd,iBAAiB,kBACjB,OAAO,OAAO;;;;UAWC;EACf;EACA;EACA;EACA;IAAY;IAAe;IAAe;;;;qBAI/B,UAAO,OAAW;;;;;;wBAQf,cACd,iBAAiB,mBAChB;;;;;;;;;;;;wBCvWa,iBAAiB;;;wBA6BjB,iBAAiB;;UAWvB;EACR;EACA;;;KA+NU;;;;EAGN;EAAgB;;;;EAEhB;EAAkB;EAAc,MAAM;;;;EAEtC;EAAiB;;;;;EAGjB;;;;;;;;;;;;wBAYU,iBACd,gBACA,uBACC"}
package/dist/index.js CHANGED
@@ -71,16 +71,660 @@ const UNITLESS_PROPERTIES = /* @__PURE__ */ new Set([
71
71
  "tab-size",
72
72
  "widows",
73
73
  "z-index",
74
- "zoom"
74
+ "zoom",
75
+ "-moz-animation-iteration-count",
76
+ "-moz-animation-name",
77
+ "-moz-box-flex",
78
+ "-moz-box-ordinal-group",
79
+ "-moz-column-count",
80
+ "-moz-columns",
81
+ "-moz-opacity",
82
+ "-moz-tab-size",
83
+ "-ms-flex",
84
+ "-ms-flex-positive",
85
+ "-ms-order",
86
+ "-o-animation-iteration-count",
87
+ "-o-animation-name",
88
+ "-o-tab-size",
89
+ "-webkit-animation-iteration-count",
90
+ "-webkit-animation-name",
91
+ "-webkit-border-image-slice",
92
+ "-webkit-box-flex",
93
+ "-webkit-box-flex-group",
94
+ "-webkit-box-ordinal-group",
95
+ "-webkit-column-count",
96
+ "-webkit-columns",
97
+ "-webkit-flex",
98
+ "-webkit-flex-grow",
99
+ "-webkit-flex-shrink",
100
+ "-webkit-line-clamp",
101
+ "-webkit-order"
75
102
  ]);
103
+ /**
104
+ * A style key's vendor prefix and the CSS prefix it stands for:
105
+ * `WebkitLineClamp` is `-webkit-line-clamp`, `msFlex` is `-ms-flex`. The
106
+ * extractor reads this table through `property_table.json`.
107
+ */
108
+ const VENDOR_PREFIXES = [
109
+ ["Webkit", "-webkit-"],
110
+ ["Moz", "-moz-"],
111
+ ["ms", "-ms-"],
112
+ ["O", "-o-"]
113
+ ];
114
+ /** The camelCase style key of a CSS property name. */
115
+ function styleKey(property) {
116
+ const vendor = VENDOR_PREFIXES.find(([, prefix]) => property.startsWith(prefix));
117
+ const [key, rest] = vendor ? [vendor[0], property.slice(vendor[1].length - 1)] : ["", property];
118
+ return key + rest.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
119
+ }
76
120
  const UNITLESS_PROPERTY_SPELLINGS = /* @__PURE__ */ new Set();
77
121
  for (const property of UNITLESS_PROPERTIES) {
78
122
  UNITLESS_PROPERTY_SPELLINGS.add(property);
79
- UNITLESS_PROPERTY_SPELLINGS.add(property.replace(/-([a-z])/g, (_, char) => char.toUpperCase()));
123
+ UNITLESS_PROPERTY_SPELLINGS.add(styleKey(property));
80
124
  }
81
- /** Accepts either spelling: `line-height` or `lineHeight`. */
125
+ /** Accepts either spelling: `line-height` or `lineHeight`,
126
+ * `-webkit-line-clamp` or `WebkitLineClamp`. */
82
127
  function isUnitlessProperty(property) {
83
128
  return UNITLESS_PROPERTY_SPELLINGS.has(property);
84
129
  }
85
130
  //#endregion
86
- export { SHORTHAND_PROPERTIES, UNITLESS_PROPERTIES, isUnitlessProperty };
131
+ //#region src/css-tokens.ts
132
+ const at = (css, index) => index < css.length ? css.charCodeAt(index) : -1;
133
+ const isNewline = (c) => c === 10 || c === 13 || c === 12;
134
+ const isWhitespace = (c) => c === 32 || c === 9 || isNewline(c);
135
+ const isDigit = (c) => c >= 48 && c <= 57;
136
+ const isHexDigit = (c) => isDigit(c) || c >= 65 && c <= 70 || c >= 97 && c <= 102;
137
+ const isIdentStart = (c) => c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 95 || c >= 128;
138
+ const isIdentChar = (c) => isIdentStart(c) || isDigit(c) || c === 45;
139
+ const isNonPrintable = (c) => c >= 0 && c <= 8 || c === 11 || c >= 14 && c <= 31 || c === 127;
140
+ /** Whether a `\` at `index` starts an escape. */
141
+ const isEscape = (css, index) => at(css, index) === 92 && !isNewline(at(css, index + 1));
142
+ function startsIdentifier(css, index) {
143
+ const c = at(css, index);
144
+ if (c === 45) {
145
+ const next = at(css, index + 1);
146
+ return isIdentStart(next) || next === 45 || isEscape(css, index + 1);
147
+ }
148
+ return isIdentStart(c) || isEscape(css, index);
149
+ }
150
+ function startsNumber(css, index) {
151
+ const signed = at(css, index) === 43 || at(css, index) === 45 ? 1 : 0;
152
+ const c = at(css, index + signed);
153
+ return isDigit(c) || c === 46 && isDigit(at(css, index + signed + 1));
154
+ }
155
+ /** The code point the escape at `index` stands for, and the index after it. */
156
+ function readEscape(css, index) {
157
+ let i = index + 1;
158
+ if (i >= css.length) return ["�", i];
159
+ if (!isHexDigit(at(css, i))) {
160
+ const char = String.fromCodePoint(css.codePointAt(i) ?? 65533);
161
+ return [char, i + char.length];
162
+ }
163
+ const digitsEnd = Math.min(i + 6, css.length);
164
+ let value = 0;
165
+ while (i < digitsEnd && isHexDigit(at(css, i))) {
166
+ value = value * 16 + parseInt(css[i], 16);
167
+ i += 1;
168
+ }
169
+ if (at(css, i) === 13 && at(css, i + 1) === 10) i += 2;
170
+ else if (isWhitespace(at(css, i))) i += 1;
171
+ const valid = value !== 0 && value <= 1114111 && (value < 55296 || value > 57343);
172
+ return [String.fromCodePoint(valid ? value : 65533), i];
173
+ }
174
+ /** The identifier sequence at `index`, decoded, and the index after it. */
175
+ function readName(css, index) {
176
+ let name = "";
177
+ let from = index;
178
+ let i = index;
179
+ for (;;) if (isIdentChar(at(css, i))) i += 1;
180
+ else if (isEscape(css, i)) {
181
+ const [char, next] = readEscape(css, i);
182
+ name += css.slice(from, i) + char;
183
+ i = next;
184
+ from = i;
185
+ } else return [name + css.slice(from, i), i];
186
+ }
187
+ /** The identifier starting at `index`, decoded, or `undefined` when none
188
+ * starts there. */
189
+ function identifierAt(css, index) {
190
+ if (!startsIdentifier(css, index)) return void 0;
191
+ const [name, end] = readName(css, index);
192
+ return {
193
+ name,
194
+ end
195
+ };
196
+ }
197
+ /** `name` decoded when it is one whole identifier, else as written. */
198
+ function decodedIdentifier(name) {
199
+ const read = identifierAt(name, 0);
200
+ return read?.end === name.length ? read.name : name;
201
+ }
202
+ function readString(css, index) {
203
+ const quote = at(css, index);
204
+ let i = index + 1;
205
+ for (;;) {
206
+ const c = at(css, i);
207
+ if (c === -1) return ["string", i];
208
+ if (c === quote) return ["string", i + 1];
209
+ if (isNewline(c)) return ["bad-string", i];
210
+ if (c === 92) {
211
+ const next = at(css, i + 1);
212
+ if (next === -1) i += 1;
213
+ else if (isNewline(next)) i += next === 13 && at(css, i + 2) === 10 ? 3 : 2;
214
+ else i = readEscape(css, i)[1];
215
+ } else i += 1;
216
+ }
217
+ }
218
+ function badUrlEnd(css, index) {
219
+ let i = index;
220
+ for (;;) {
221
+ const c = at(css, i);
222
+ if (c === -1) return i;
223
+ if (c === 41) return i + 1;
224
+ i = isEscape(css, i) ? readEscape(css, i)[1] : i + 1;
225
+ }
226
+ }
227
+ /** An unquoted `url(` body starting at `index`. */
228
+ function readUrl(css, index) {
229
+ let i = index;
230
+ while (isWhitespace(at(css, i))) i += 1;
231
+ for (;;) {
232
+ const c = at(css, i);
233
+ if (c === -1) return ["url", i];
234
+ if (c === 41) return ["url", i + 1];
235
+ if (isWhitespace(c)) {
236
+ while (isWhitespace(at(css, i))) i += 1;
237
+ const next = at(css, i);
238
+ if (next === -1) return ["url", i];
239
+ if (next === 41) return ["url", i + 1];
240
+ return ["bad-url", badUrlEnd(css, i)];
241
+ }
242
+ if (c === 34 || c === 39 || c === 40 || isNonPrintable(c)) return ["bad-url", badUrlEnd(css, i)];
243
+ if (c === 92) {
244
+ if (!isEscape(css, i)) return ["bad-url", badUrlEnd(css, i)];
245
+ i = readEscape(css, i)[1];
246
+ } else i += 1;
247
+ }
248
+ }
249
+ function numberEnd(css, index) {
250
+ let i = index;
251
+ if (at(css, i) === 43 || at(css, i) === 45) i += 1;
252
+ while (isDigit(at(css, i))) i += 1;
253
+ if (at(css, i) === 46 && isDigit(at(css, i + 1))) {
254
+ i += 2;
255
+ while (isDigit(at(css, i))) i += 1;
256
+ }
257
+ const e = at(css, i);
258
+ if (e === 69 || e === 101) {
259
+ const sign = at(css, i + 1) === 43 || at(css, i + 1) === 45 ? 1 : 0;
260
+ if (isDigit(at(css, i + 1 + sign))) {
261
+ i += 2 + sign;
262
+ while (isDigit(at(css, i))) i += 1;
263
+ }
264
+ }
265
+ return i;
266
+ }
267
+ const SINGLE = new Map([
268
+ "(",
269
+ ")",
270
+ "[",
271
+ "]",
272
+ "{",
273
+ "}",
274
+ ",",
275
+ ":",
276
+ ";"
277
+ ].map((c) => [c, c]));
278
+ /** Every token of `css`, in order. */
279
+ function tokenize(css) {
280
+ const tokens = [];
281
+ let index = 0;
282
+ const push = (type, end, value = "", number = 0) => {
283
+ tokens.push({
284
+ type,
285
+ start: index,
286
+ end,
287
+ value,
288
+ number
289
+ });
290
+ index = end;
291
+ };
292
+ while (index < css.length) {
293
+ const c = css.charCodeAt(index);
294
+ if (c === 47 && at(css, index + 1) === 42) {
295
+ const close = css.indexOf("*/", index + 2);
296
+ index = close === -1 ? css.length : close + 2;
297
+ } else if (isWhitespace(c)) {
298
+ let end = index + 1;
299
+ while (isWhitespace(at(css, end))) end += 1;
300
+ push("whitespace", end);
301
+ } else if (c === 34 || c === 39) {
302
+ const [type, end] = readString(css, index);
303
+ push(type, end);
304
+ } else if (startsNumber(css, index)) {
305
+ const end = numberEnd(css, index);
306
+ const number = Number(css.slice(index, end));
307
+ if (startsIdentifier(css, end)) {
308
+ const [unit, unitEnd] = readName(css, end);
309
+ push("dimension", unitEnd, unit, number);
310
+ } else if (at(css, end) === 37) push("percentage", end + 1, "", number);
311
+ else push("number", end, "", number);
312
+ } else if (c === 45 && css.startsWith("->", index + 1)) push("CDC", index + 3);
313
+ else if (startsIdentifier(css, index)) {
314
+ const [name, end] = readName(css, index);
315
+ if (at(css, end) !== 40) push("ident", end, name);
316
+ else if (!/^url$/i.test(name)) push("function", end + 1, name);
317
+ else {
318
+ let body = end + 1;
319
+ while (isWhitespace(at(css, body)) && isWhitespace(at(css, body + 1))) body += 1;
320
+ const first = at(css, body);
321
+ const quote = isWhitespace(first) ? at(css, body + 1) : first;
322
+ if (quote === 34 || quote === 39) push("function", body, name);
323
+ else {
324
+ const [type, urlEnd] = readUrl(css, end + 1);
325
+ push(type, urlEnd);
326
+ }
327
+ }
328
+ } else if (c === 35 && (isIdentChar(at(css, index + 1)) || isEscape(css, index + 1))) {
329
+ const [name, end] = readName(css, index + 1);
330
+ push("hash", end, name);
331
+ } else if (c === 64 && startsIdentifier(css, index + 1)) {
332
+ const [name, end] = readName(css, index + 1);
333
+ push("at-keyword", end, name);
334
+ } else if (c === 60 && css.startsWith("!--", index + 1)) push("CDO", index + 4);
335
+ else {
336
+ const single = SINGLE.get(css[index]);
337
+ if (single === void 0) push("delim", index + 1, css[index]);
338
+ else push(single, index + 1);
339
+ }
340
+ }
341
+ return tokens;
342
+ }
343
+ const CLOSERS = /* @__PURE__ */ new Map([
344
+ ["function", ")"],
345
+ ["(", ")"],
346
+ ["[", "]"],
347
+ ["{", "}"]
348
+ ]);
349
+ /**
350
+ * `tokens` as component values. Only the innermost open block's own closer
351
+ * closes it; any other closer stays a token, and a block left open closes at
352
+ * the end, as CSS reads them.
353
+ */
354
+ function componentValues(tokens) {
355
+ const root = [];
356
+ const open = [];
357
+ for (const token of tokens) {
358
+ const top = open.at(-1);
359
+ if (top !== void 0 && token.type === top.closer) {
360
+ top.block.close = token;
361
+ top.block.end = token.end;
362
+ open.pop();
363
+ continue;
364
+ }
365
+ const list = top === void 0 ? root : top.block.children;
366
+ const closer = CLOSERS.get(token.type);
367
+ if (closer === void 0) list.push({
368
+ kind: "token",
369
+ token,
370
+ start: token.start,
371
+ end: token.end
372
+ });
373
+ else {
374
+ const block = {
375
+ kind: "block",
376
+ open: token,
377
+ close: void 0,
378
+ children: [],
379
+ start: token.start,
380
+ end: token.end
381
+ };
382
+ list.push(block);
383
+ open.push({
384
+ block,
385
+ closer
386
+ });
387
+ }
388
+ }
389
+ for (let i = open.length - 1; i >= 0; i -= 1) {
390
+ const { block } = open[i];
391
+ block.end = Math.max(block.end, block.children.at(-1)?.end ?? block.end);
392
+ }
393
+ return root;
394
+ }
395
+ /** Whether `test` holds for a value at any depth. */
396
+ function someValue(values, test) {
397
+ return values.some((value) => test(value) || value.kind === "block" && someValue(value.children, test));
398
+ }
399
+ /** Whether `value` is whitespace. */
400
+ const isSpace = (value) => value?.kind === "token" && value.token.type === "whitespace";
401
+ /**
402
+ * Every `var()` call in `values` at any depth, fallbacks included, outer
403
+ * calls first: a function whose decoded name is `var` in any case, and whose
404
+ * first argument is a custom-property name.
405
+ */
406
+ function variableReads(values) {
407
+ const reads = [];
408
+ const visit = (list) => {
409
+ for (const value of list) {
410
+ if (value.kind !== "block") continue;
411
+ if (value.open.type === "function" && /^var$/i.test(value.open.value)) {
412
+ const [name, comma, first] = value.children.filter((c) => !isSpace(c));
413
+ if (name?.kind === "token" && name.token.type === "ident" && name.token.value.startsWith("--")) {
414
+ const end = value.close?.start ?? value.end;
415
+ reads.push({
416
+ name: name.token.value,
417
+ start: value.start,
418
+ end: value.end,
419
+ fallback: comma?.kind === "token" && comma.token.type === "," ? {
420
+ start: comma.end,
421
+ first: first?.start ?? end,
422
+ end
423
+ } : void 0
424
+ });
425
+ }
426
+ }
427
+ visit(value.children);
428
+ }
429
+ };
430
+ visit(values);
431
+ return reads;
432
+ }
433
+ //#endregion
434
+ //#region src/registration-values.ts
435
+ /**
436
+ * Reads a custom-property registration's initial value as CSS component
437
+ * values: what it substitutes, which units make it depend on context, and
438
+ * what an all-absolute math function computes to. Tokens come from the shared
439
+ * CSS reader, so comments are trivia, quoted strings and `url()` stay text,
440
+ * and escaped names are decoded.
441
+ */
442
+ const asciiLower = (text) => text.replace(/[A-Z]/g, (letter) => letter.toLowerCase());
443
+ const parse = (text) => componentValues(tokenize(text));
444
+ /** A function's ASCII-lowercase name, `''` for a parenthesized block, and
445
+ * `undefined` for anything else. */
446
+ function functionName(value) {
447
+ if (value?.kind !== "block") return void 0;
448
+ if (value.open.type === "function") return asciiLower(value.open.value);
449
+ return value.open.type === "(" ? "" : void 0;
450
+ }
451
+ const SUBSTITUTIONS = /* @__PURE__ */ new Set([
452
+ "var",
453
+ "env",
454
+ "attr"
455
+ ]);
456
+ /** Whether `value` substitutes another value (`var()`, `env()`, `attr()`),
457
+ * which no registration's initial value may. */
458
+ function substitutesValue(value) {
459
+ return someValue(parse(value), (component) => SUBSTITUTIONS.has(functionName(component) ?? ""));
460
+ }
461
+ const CONTEXT_UNITS = /* @__PURE__ */ new Set([
462
+ "em",
463
+ "rem",
464
+ "ex",
465
+ "rex",
466
+ "cap",
467
+ "rcap",
468
+ "ch",
469
+ "rch",
470
+ "ic",
471
+ "ric",
472
+ "lh",
473
+ "rlh",
474
+ "cqw",
475
+ "cqh",
476
+ "cqi",
477
+ "cqb",
478
+ "cqmin",
479
+ "cqmax"
480
+ ]);
481
+ /** Whether `value` uses a font- or container-relative unit, inside math
482
+ * functions too, which makes a typed initial value depend on context. */
483
+ function dependsOnContext(value) {
484
+ return someValue(parse(value), (component) => component.kind === "token" && component.token.type === "dimension" && CONTEXT_UNITS.has(asciiLower(component.token.value)));
485
+ }
486
+ /** Absolute units, by the canonical unit each converts to. */
487
+ const ABSOLUTE_UNITS = /* @__PURE__ */ new Map([
488
+ ["px", ["length", 1]],
489
+ ["cm", ["length", 96 / 2.54]],
490
+ ["mm", ["length", 96 / 25.4]],
491
+ ["q", ["length", 96 / 101.6]],
492
+ ["in", ["length", 96]],
493
+ ["pt", ["length", 4 / 3]],
494
+ ["pc", ["length", 16]],
495
+ ["deg", ["angle", 1]],
496
+ ["grad", ["angle", .9]],
497
+ ["rad", ["angle", 180 / Math.PI]],
498
+ ["turn", ["angle", 360]],
499
+ ["s", ["time", 1]],
500
+ ["ms", ["time", .001]],
501
+ ["dppx", ["resolution", 1]],
502
+ ["x", ["resolution", 1]],
503
+ ["dpi", ["resolution", 1 / 96]],
504
+ ["dpcm", ["resolution", 2.54 / 96]]
505
+ ]);
506
+ const CANONICAL_UNITS = {
507
+ number: "",
508
+ length: "px",
509
+ angle: "deg",
510
+ time: "s",
511
+ resolution: "dppx"
512
+ };
513
+ const MATH_FUNCTIONS = /* @__PURE__ */ new Set([
514
+ "calc",
515
+ "min",
516
+ "max",
517
+ "clamp"
518
+ ]);
519
+ const operatorOf = (value) => value?.kind === "token" && value.token.type === "delim" && "+-*/".includes(value.token.value) ? value.token.value : void 0;
520
+ /**
521
+ * A math function's argument read as CSS calculation syntax, or what is out
522
+ * of place: `+` and `-` need whitespace on both sides, every operator needs a
523
+ * value on each side, and two values need an operator between them.
524
+ */
525
+ function readCalculation(values) {
526
+ let start = 0;
527
+ let end = values.length;
528
+ while (start < end && isSpace(values[start])) start += 1;
529
+ while (end > start && isSpace(values[end - 1])) end -= 1;
530
+ if (start === end) return { invalid: "a calculation needs a value" };
531
+ let at = start;
532
+ const skipSpace = () => {
533
+ while (at < end && isSpace(values[at])) at += 1;
534
+ };
535
+ const operand = () => {
536
+ skipSpace();
537
+ const value = values[at];
538
+ if (at >= end || operatorOf(value) !== void 0) return { invalid: "an operator needs a value on each side" };
539
+ at += 1;
540
+ return { value };
541
+ };
542
+ const product = () => {
543
+ let left = operand();
544
+ while (!("invalid" in left)) {
545
+ const before = at;
546
+ skipSpace();
547
+ const op = operatorOf(values[at]);
548
+ if (op !== "*" && op !== "/") {
549
+ at = before;
550
+ return left;
551
+ }
552
+ at += 1;
553
+ const right = operand();
554
+ if ("invalid" in right) return right;
555
+ left = {
556
+ op,
557
+ left,
558
+ right
559
+ };
560
+ }
561
+ return left;
562
+ };
563
+ let sum = product();
564
+ while (!("invalid" in sum) && at < end) {
565
+ const spaced = isSpace(values[at]);
566
+ skipSpace();
567
+ const op = operatorOf(values[at]);
568
+ if (op !== "+" && op !== "-") return { invalid: "two values need an operator between them" };
569
+ if (at + 1 >= end) return { invalid: "an operator needs a value on each side" };
570
+ if (!spaced || !isSpace(values[at + 1])) return { invalid: "\"+\" and \"-\" need whitespace on both sides" };
571
+ at += 1;
572
+ const right = product();
573
+ if ("invalid" in right) return right;
574
+ sum = {
575
+ op,
576
+ left: sum,
577
+ right
578
+ };
579
+ }
580
+ return sum;
581
+ }
582
+ /** Both sides evaluated, so a broken expression on either side is reported
583
+ * even where the other cannot be evaluated. */
584
+ function combine(op, left, right) {
585
+ if (left !== void 0 && "invalid" in left) return left;
586
+ if (right !== void 0 && "invalid" in right) return right;
587
+ if (left === void 0 || right === void 0) return void 0;
588
+ if (op === "+" || op === "-") {
589
+ if (left.type !== right.type) return void 0;
590
+ return {
591
+ type: left.type,
592
+ value: op === "+" ? left.value + right.value : left.value - right.value
593
+ };
594
+ }
595
+ if (op === "*") {
596
+ if (left.type !== "number" && right.type !== "number") return void 0;
597
+ return {
598
+ type: left.type === "number" ? right.type : left.type,
599
+ value: left.value * right.value
600
+ };
601
+ }
602
+ if (right.type === "number") return {
603
+ type: left.type,
604
+ value: left.value / right.value
605
+ };
606
+ return right.type === left.type ? {
607
+ type: "number",
608
+ value: left.value / right.value
609
+ } : void 0;
610
+ }
611
+ /** The calculation constants, by their ASCII-lowercase name. */
612
+ const CONSTANTS = /* @__PURE__ */ new Map([
613
+ ["e", Math.E],
614
+ ["pi", Math.PI],
615
+ ["infinity", Infinity],
616
+ ["-infinity", -Infinity],
617
+ ["nan", NaN]
618
+ ]);
619
+ function evaluate(node) {
620
+ if ("op" in node) return combine(node.op, evaluate(node.left), evaluate(node.right));
621
+ const { value } = node;
622
+ if (value.kind === "block") return mathFunction(value);
623
+ const { token } = value;
624
+ if (token.type === "number") return {
625
+ type: "number",
626
+ value: token.number
627
+ };
628
+ if (token.type === "dimension") {
629
+ const unit = ABSOLUTE_UNITS.get(asciiLower(token.value));
630
+ return unit && {
631
+ type: unit[0],
632
+ value: token.number * unit[1]
633
+ };
634
+ }
635
+ const constant = token.type === "ident" ? CONSTANTS.get(asciiLower(token.value)) : void 0;
636
+ return constant === void 0 ? void 0 : {
637
+ type: "number",
638
+ value: constant
639
+ };
640
+ }
641
+ /** `calc()`, `min()`, `max()` and `clamp()` over numbers and absolute
642
+ * dimensions, nested ones and parenthesized blocks included. */
643
+ function mathFunction(value) {
644
+ const name = functionName(value);
645
+ if (name === void 0 || name !== "" && !MATH_FUNCTIONS.has(name)) return;
646
+ const parts = [[]];
647
+ for (const arg of value.children) if (arg.kind === "token" && arg.token.type === ",") parts.push([]);
648
+ else parts[parts.length - 1].push(arg);
649
+ if ((name === "" || name === "calc") && parts.length > 1) return { invalid: "commas separate only the arguments of min(), max() and clamp()" };
650
+ const args = [];
651
+ let unsupported = false;
652
+ for (const part of parts) {
653
+ const calculation = readCalculation(part);
654
+ if ("invalid" in calculation) return calculation;
655
+ const computed = evaluate(calculation);
656
+ if (computed !== void 0 && "invalid" in computed) return computed;
657
+ if (computed === void 0) unsupported = true;
658
+ else args.push(computed);
659
+ }
660
+ if (unsupported) return void 0;
661
+ if (name === "" || name === "calc") return args[0];
662
+ const type = args[0].type;
663
+ if (args.some((arg) => arg.type !== type)) return void 0;
664
+ const values = args.map((arg) => arg.value);
665
+ if (name === "min") return {
666
+ type,
667
+ value: Math.min(...values)
668
+ };
669
+ if (name === "max") return {
670
+ type,
671
+ value: Math.max(...values)
672
+ };
673
+ if (values.length !== 3) return void 0;
674
+ const [low, middle, high] = values;
675
+ return {
676
+ type,
677
+ value: Math.max(low, Math.min(middle, high))
678
+ };
679
+ }
680
+ /** The data type each folded value is written as, by syntax type. */
681
+ const ACCEPTING_SYNTAX_TYPES = /* @__PURE__ */ new Map([
682
+ ["number", "number"],
683
+ ["integer", "number"],
684
+ ["length", "length"],
685
+ ["length-percentage", "length"],
686
+ ["angle", "angle"],
687
+ ["time", "time"],
688
+ ["resolution", "resolution"]
689
+ ]);
690
+ /**
691
+ * The computed value of an initial value that is one math function (`calc()`,
692
+ * `min()`, `max()`, `clamp()`) over numbers and absolute units, such as
693
+ * `calc(1px / 1px)` → `1`. The browser computes the same value, and CSS
694
+ * tooling that cannot parse the function in that position reads the result.
695
+ * The syntax's alternatives are tried in their declared order, and an
696
+ * `<integer>` one rounds a number result to the nearest integer, halves
697
+ * toward positive infinity, as CSS does. `undefined` for anything else, and
698
+ * for the universal syntax, whose initial value is kept as written.
699
+ */
700
+ function foldInitialValue(syntax, initialValue) {
701
+ if (syntax.trim() === "*") return void 0;
702
+ const values = parse(initialValue).filter((value) => !isSpace(value));
703
+ const only = values.length === 1 ? values[0] : void 0;
704
+ if (only?.kind !== "block" || !MATH_FUNCTIONS.has(functionName(only) ?? "")) return;
705
+ const computed = mathFunction(only);
706
+ if (computed === void 0) return void 0;
707
+ if ("invalid" in computed) return {
708
+ kind: "invalid",
709
+ reason: computed.invalid
710
+ };
711
+ if (!Number.isFinite(computed.value)) return { kind: "not-finite" };
712
+ for (const component of syntax.split("|")) {
713
+ const type = /<([a-z-]+)>/.exec(component)?.[1];
714
+ if (type === void 0 || ACCEPTING_SYNTAX_TYPES.get(type) !== computed.type) continue;
715
+ return {
716
+ kind: "folded",
717
+ text: type === "integer" ? BigInt(Math.round(computed.value)).toString() : written(computed)
718
+ };
719
+ }
720
+ return {
721
+ kind: "rejected",
722
+ text: written(computed),
723
+ type: computed.type
724
+ };
725
+ }
726
+ function written(computed) {
727
+ return `${Number(computed.value.toPrecision(12))}${CANONICAL_UNITS[computed.type]}`;
728
+ }
729
+ //#endregion
730
+ export { SHORTHAND_PROPERTIES, UNITLESS_PROPERTIES, componentValues, decodedIdentifier, dependsOnContext, foldInitialValue, identifierAt, isSpace, isUnitlessProperty, someValue, substitutesValue, tokenize, variableReads };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@animus-ui/properties",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "CSS property classification data — unitless set, shorthand list, type re-exports",
5
5
  "keywords": [
6
6
  "animus",
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "scripts": {
38
38
  "build": "bun run build:ts",
39
- "build:ts": "tsdown && tsc -p tsconfig.build.json",
39
+ "build:ts": "tsdown",
40
40
  "compile": "tsc --noEmit"
41
41
  },
42
42
  "dependencies": {},
@@ -1,5 +0,0 @@
1
- /**
2
- * Covers only shorthands registered in the prop system, not all CSS shorthands.
3
- */
4
- export declare const SHORTHAND_PROPERTIES: readonly ['border', 'borderTop', 'borderBottom', 'borderLeft', 'borderRight', 'borderWidth', 'borderStyle', 'borderColor', 'background', 'flex', 'margin', 'padding', 'transition', 'gap', 'grid', 'gridArea', 'gridColumn', 'gridRow', 'gridTemplate', 'overflow'];
5
- //# sourceMappingURL=shorthands.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"shorthands.d.ts","sourceRoot":"","sources":["../src/shorthands.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,oBAAoB,YAC/B,QAAQ,EACR,WAAW,EACX,cAAc,EACd,YAAY,EACZ,aAAa,EACb,aAAa,EACb,aAAa,EACb,aAAa,EACb,YAAY,EACZ,MAAM,EACN,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,KAAK,EACL,MAAM,EACN,UAAU,EACV,YAAY,EACZ,SAAS,EACT,cAAc,EACd,UAAU,CACF,CAAC"}
@@ -1,4 +0,0 @@
1
- export declare const UNITLESS_PROPERTIES: Set<string>;
2
- /** Accepts either spelling: `line-height` or `lineHeight`. */
3
- export declare function isUnitlessProperty(property: string): boolean;
4
- //# sourceMappingURL=unitless.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"unitless.d.ts","sourceRoot":"","sources":["../src/unitless.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,aAgD9B,CAAC;AAUH,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE5D"}