@ax-design/typed-om 0.2.1 → 0.3.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.
- package/README.md +17 -1
- package/dist/detector.d.ts +5 -0
- package/dist/detector.js +17 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/parse.d.ts +1 -0
- package/dist/parse.js +10 -0
- package/dist/probe.d.ts +1 -0
- package/dist/probe.js +13 -1
- package/dist/reader.js +6 -2
- package/dist/register.js +17 -2
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +3 -1
- package/package.json +1 -1
- package/src/detector.ts +25 -2
- package/src/index.ts +2 -2
- package/src/parse.ts +11 -0
- package/src/probe.ts +14 -1
- package/src/reader.ts +6 -2
- package/src/register.ts +14 -2
- package/src/schema.ts +5 -1
package/README.md
CHANGED
|
@@ -54,10 +54,24 @@ binding.get('--my-color'); // [255, 0, 0, 1]
|
|
|
54
54
|
binding.get('--my-mode'); // 'soft'
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
Values in common forms (`12px`, `50%`, `calc(50% + 4px)`, `rgb()`, `#hex`, keywords) are parsed directly. Anything else (`calc(1em + 2px)`, `red`) goes through a hidden probe element, so the browser does the math.
|
|
57
|
+
Values in common forms (`12px`, `50%`, `calc(50% + 4px)`, `rgb()`, `#hex`, `200ms`, `0.2s`, keywords) are parsed directly. Anything else (`calc(1em + 2px)`, `red`) goes through a hidden probe element, so the browser does the math.
|
|
58
58
|
|
|
59
59
|
`register: true` registers the property with `CSS.registerProperty` where it exists, so your transitions on it interpolate. Where it does not exist, everything still works and transitions jump to the end value.
|
|
60
60
|
|
|
61
|
+
### Times
|
|
62
|
+
|
|
63
|
+
A `time` property holds a CSS `<time>` and reads as a number of milliseconds, whichever unit the author wrote:
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
const schema = new StyleSchema([
|
|
67
|
+
{ name: '--my-delay', syntax: { type: 'time' }, initial: '250ms', inherits: true, register: true },
|
|
68
|
+
]);
|
|
69
|
+
// --my-delay: 0.4s
|
|
70
|
+
binding.get('--my-delay'); // 400
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
A bare number is not a time, as in CSS: `--my-delay: 400` keeps the initial value. `calc()` and other forms go through the probe like the other types.
|
|
74
|
+
|
|
61
75
|
### Watching changes
|
|
62
76
|
|
|
63
77
|
```javascript
|
|
@@ -80,3 +94,5 @@ A property can be skipped when another property makes it irrelevant, which saves
|
|
|
80
94
|
## Browser support
|
|
81
95
|
|
|
82
96
|
Typed OM works in browsers released since 2016 that support custom properties and Shadow DOM. Keyword values cannot be CSS-wide keywords (`initial`, `inherit` and so on), and changes of a keyword to `none` are not seen by the detector.
|
|
97
|
+
|
|
98
|
+
The detector sees changes of a `time` property only where `CSS.registerProperty` exists (a time can only transition as a registered property). Elsewhere `read()` still gives the right value: read again when you know it may have changed.
|
package/dist/detector.d.ts
CHANGED
|
@@ -6,6 +6,11 @@ export interface KeywordMirror {
|
|
|
6
6
|
initial: string;
|
|
7
7
|
}
|
|
8
8
|
export declare function keywordMirrors(schema: PropertySpec[]): KeywordMirror[];
|
|
9
|
+
export interface TimeMirror {
|
|
10
|
+
name: string;
|
|
11
|
+
property: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function timeMirrors(schema: PropertySpec[]): TimeMirror[];
|
|
9
14
|
export interface DetectorTemplate {
|
|
10
15
|
css: string;
|
|
11
16
|
elementCount: number;
|
package/dist/detector.js
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
// - numeric values: folded into up to four weighted calc() hashes, placed in the x and y of
|
|
5
5
|
// transform-origin and perspective-origin (neither triggers layout), 1ms transitions;
|
|
6
6
|
// - colors: one real color property each, 1ms transitions;
|
|
7
|
-
// - keywords: an animation-name list with shadow-scoped empty @keyframes per allowed value
|
|
7
|
+
// - keywords: an animation-name list with shadow-scoped empty @keyframes per allowed value;
|
|
8
|
+
// - times: one registered <time> mirror each, 1ms transitions. A time cannot enter the numeric
|
|
9
|
+
// hash (calc() multiplies a time by numbers only), and only a registered custom property
|
|
10
|
+
// interpolates a time, so times are observed where CSS.registerProperty exists.
|
|
8
11
|
// Any transition or animation start marks the host as changed.
|
|
9
12
|
const HASH_SLOTS = 4;
|
|
10
13
|
const COLOR_PROPERTIES = [
|
|
@@ -54,10 +57,15 @@ export function keywordMirrors(schema) {
|
|
|
54
57
|
}
|
|
55
58
|
return out;
|
|
56
59
|
}
|
|
60
|
+
// One registered <time> mirror per time spec, named by its position among the time specs. The
|
|
61
|
+
// definition is the same for every schema (<time>, not inherited, 0s), so schemas share the names.
|
|
62
|
+
export function timeMirrors(schema) {
|
|
63
|
+
return schema.filter((spec) => spec.syntax.type === 'time').map((spec, i) => ({ name: `--${PREFIX}-t${i}`, property: spec.name }));
|
|
64
|
+
}
|
|
57
65
|
// `registered` lists the properties registered by name. A registered non-inherited property
|
|
58
66
|
// does not reach the detector element inside the shadow root, so the host mirrors it into an
|
|
59
67
|
// unregistered (inherited) property. The slot resets the mirrors so author content does not
|
|
60
|
-
// inherit them.
|
|
68
|
+
// inherit them. Time specs are observed only when their time mirror is in `registered`.
|
|
61
69
|
export function buildDetector(schema, registered = new Set()) {
|
|
62
70
|
const mirrors = [];
|
|
63
71
|
const kwMirrors = [];
|
|
@@ -111,6 +119,13 @@ export function buildDetector(schema, registered = new Set()) {
|
|
|
111
119
|
e.transitions.push(`${COLOR_PROPERTIES[i]} 1ms`);
|
|
112
120
|
});
|
|
113
121
|
});
|
|
122
|
+
for (const m of timeMirrors(schema)) {
|
|
123
|
+
if (!registered.has(m.name))
|
|
124
|
+
continue;
|
|
125
|
+
const spec = schema.filter((s) => s.name === m.property)[0];
|
|
126
|
+
elements[0].decls.push(`${m.name}: var(${source(spec)}, ${spec.initial});`);
|
|
127
|
+
elements[0].transitions.push(`${m.name} 1ms`);
|
|
128
|
+
}
|
|
114
129
|
const rules = [];
|
|
115
130
|
const keywords = schema.filter((s) => s.syntax.type === 'keyword');
|
|
116
131
|
if (keywords.length) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { StyleBinding, StyleSchema } from './binding.js';
|
|
2
2
|
export { attachDetector, buildDetector } from './detector.js';
|
|
3
3
|
export type { DetectorHandle, DetectorTemplate } from './detector.js';
|
|
4
|
-
export { parseColor, parseKeyword, parseLength, parseLengthPercentage, parseNumber } from './parse.js';
|
|
5
|
-
export { probeColor, probeLengthPercentage, probeNumber } from './probe.js';
|
|
4
|
+
export { parseColor, parseKeyword, parseLength, parseLengthPercentage, parseNumber, parseTime } from './parse.js';
|
|
5
|
+
export { probeColor, probeLengthPercentage, probeNumber, probeTime } from './probe.js';
|
|
6
6
|
export { StyleReader } from './reader.js';
|
|
7
7
|
export type { ReadStats } from './reader.js';
|
|
8
8
|
export { canRegister, registerProperties } from './register.js';
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { StyleBinding, StyleSchema } from './binding.js';
|
|
2
2
|
export { attachDetector, buildDetector } from './detector.js';
|
|
3
|
-
export { parseColor, parseKeyword, parseLength, parseLengthPercentage, parseNumber } from './parse.js';
|
|
4
|
-
export { probeColor, probeLengthPercentage, probeNumber } from './probe.js';
|
|
3
|
+
export { parseColor, parseKeyword, parseLength, parseLengthPercentage, parseNumber, parseTime } from './parse.js';
|
|
4
|
+
export { probeColor, probeLengthPercentage, probeNumber, probeTime } from './probe.js';
|
|
5
5
|
export { StyleReader } from './reader.js';
|
|
6
6
|
export { canRegister, registerProperties } from './register.js';
|
|
7
7
|
export { cssSyntax, validateSchema } from './schema.js';
|
package/dist/parse.d.ts
CHANGED
|
@@ -2,5 +2,6 @@ import type { LengthPercentage, Rgba } from './schema.js';
|
|
|
2
2
|
export declare function parseNumber(text: string): number | null;
|
|
3
3
|
export declare function parseLengthPercentage(text: string): LengthPercentage | null;
|
|
4
4
|
export declare function parseLength(text: string): number | null;
|
|
5
|
+
export declare function parseTime(text: string): number | null;
|
|
5
6
|
export declare function parseKeyword(text: string, values: string[]): string | null;
|
|
6
7
|
export declare function parseColor(text: string): Rgba | null;
|
package/dist/parse.js
CHANGED
|
@@ -30,6 +30,16 @@ export function parseLength(text) {
|
|
|
30
30
|
const m = LENGTH.exec(text);
|
|
31
31
|
return m && m[2] === 'px' ? parseFloat(m[1]) : null;
|
|
32
32
|
}
|
|
33
|
+
const TIME = new RegExp(`^(${NUMBER})(ms|s)$`);
|
|
34
|
+
// A <time> in ms. Engines serialize computed times in seconds ("0.05s"); an unregistered value
|
|
35
|
+
// comes back as written, in either unit. A bare number is not a time, as in CSS.
|
|
36
|
+
export function parseTime(text) {
|
|
37
|
+
const m = TIME.exec(text);
|
|
38
|
+
if (!m)
|
|
39
|
+
return null;
|
|
40
|
+
const v = parseFloat(m[1]);
|
|
41
|
+
return m[2] === 's' ? v * 1000 : v;
|
|
42
|
+
}
|
|
33
43
|
export function parseKeyword(text, values) {
|
|
34
44
|
return values.indexOf(text) >= 0 ? text : null;
|
|
35
45
|
}
|
package/dist/probe.d.ts
CHANGED
|
@@ -2,3 +2,4 @@ import type { LengthPercentage, Rgba } from './schema.js';
|
|
|
2
2
|
export declare function probeLengthPercentage(parent: Node, name: string): LengthPercentage;
|
|
3
3
|
export declare function probeNumber(parent: Node, name: string): number;
|
|
4
4
|
export declare function probeColor(parent: Node, name: string): Rgba | null;
|
|
5
|
+
export declare function probeTime(parent: Node, name: string): number | null;
|
package/dist/probe.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseColor } from './parse.js';
|
|
1
|
+
import { parseColor, parseTime } from './parse.js';
|
|
2
2
|
// Slow path for values the fast parsers cannot read (unregistered `calc(1em + 2px)`, named
|
|
3
3
|
// colors, other units). One probe per document is moved into the shadow root that needs it,
|
|
4
4
|
// so it inherits the host's custom properties, read, and removed again.
|
|
@@ -38,6 +38,7 @@ function withProbe(parent, read) {
|
|
|
38
38
|
probe.narrow.style.marginLeft = '';
|
|
39
39
|
probe.wide.style.marginLeft = '';
|
|
40
40
|
probe.narrow.style.color = '';
|
|
41
|
+
probe.narrow.style.transitionDelay = '';
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
export function probeLengthPercentage(parent, name) {
|
|
@@ -64,3 +65,14 @@ export function probeColor(parent, name) {
|
|
|
64
65
|
return text === sentinel ? null : parseColor(text);
|
|
65
66
|
});
|
|
66
67
|
}
|
|
68
|
+
// A time in ms, or null when the value is not a valid time. transition-delay is not inherited,
|
|
69
|
+
// so an invalid value would read as its initial 0s, the same as a real 0s. The second entry of
|
|
70
|
+
// the list tells them apart: an invalid var() invalidates the whole declaration, which leaves
|
|
71
|
+
// one entry instead of two. Negative delays are valid, so negative times read back too.
|
|
72
|
+
export function probeTime(parent, name) {
|
|
73
|
+
return withProbe(parent, (p) => {
|
|
74
|
+
p.narrow.style.transitionDelay = `var(${name}), 0s`;
|
|
75
|
+
const list = getComputedStyle(p.narrow).transitionDelay.split(',');
|
|
76
|
+
return list.length === 2 ? parseTime(list[0].trim()) : null;
|
|
77
|
+
});
|
|
78
|
+
}
|
package/dist/reader.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { parseColor, parseKeyword, parseLength, parseLengthPercentage, parseNumber } from './parse.js';
|
|
2
|
-
import { probeColor, probeLengthPercentage, probeNumber } from './probe.js';
|
|
1
|
+
import { parseColor, parseKeyword, parseLength, parseLengthPercentage, parseNumber, parseTime } from './parse.js';
|
|
2
|
+
import { probeColor, probeLengthPercentage, probeNumber, probeTime } from './probe.js';
|
|
3
3
|
// Reads the computed values of a schema from one element. Fast path: literal parsing of
|
|
4
4
|
// getComputedStyle strings. Slow path: the shared probe, inserted into `probeParent`, which
|
|
5
5
|
// must be inside the element's shadow root (or otherwise inherit its custom properties).
|
|
@@ -52,6 +52,8 @@ function parseFast(spec, text) {
|
|
|
52
52
|
if (spec.syntax.keywords && spec.syntax.keywords.indexOf(text) >= 0)
|
|
53
53
|
return text;
|
|
54
54
|
return parseNumber(text);
|
|
55
|
+
case 'time':
|
|
56
|
+
return parseTime(text);
|
|
55
57
|
case 'color':
|
|
56
58
|
if (spec.syntax.keywords && spec.syntax.keywords.indexOf(text) >= 0)
|
|
57
59
|
return text;
|
|
@@ -68,6 +70,8 @@ function parseSlow(spec, parent) {
|
|
|
68
70
|
return probeLengthPercentage(parent, spec.name);
|
|
69
71
|
case 'number':
|
|
70
72
|
return probeNumber(parent, spec.name);
|
|
73
|
+
case 'time':
|
|
74
|
+
return probeTime(parent, spec.name);
|
|
71
75
|
case 'color':
|
|
72
76
|
return probeColor(parent, spec.name);
|
|
73
77
|
case 'keyword':
|
package/dist/register.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { keywordMirrors } from './detector.js';
|
|
1
|
+
import { keywordMirrors, timeMirrors } from './detector.js';
|
|
2
2
|
import { cssSyntax } from './schema.js';
|
|
3
3
|
export function canRegister() {
|
|
4
4
|
return typeof CSS !== 'undefined' && typeof CSS.registerProperty === 'function';
|
|
5
5
|
}
|
|
6
6
|
// Registers the properties that ask for it. Returns the names that are registered after the
|
|
7
7
|
// call, including ones another copy of the package registered before (first registration
|
|
8
|
-
// wins, so definitions must never change between versions)
|
|
8
|
+
// wins, so definitions must never change between versions), and the time mirrors of the
|
|
9
|
+
// detector (see timeMirrors).
|
|
9
10
|
export function registerProperties(schema) {
|
|
10
11
|
const registered = new Set();
|
|
11
12
|
if (!canRegister())
|
|
@@ -40,5 +41,19 @@ export function registerProperties(schema) {
|
|
|
40
41
|
throw error;
|
|
41
42
|
}
|
|
42
43
|
}
|
|
44
|
+
// Time specs get a registered <time> mirror on the detector element, the only way to make a
|
|
45
|
+
// time transition (and so be observed). Same definition for every schema, like the above.
|
|
46
|
+
for (const m of timeMirrors(schema)) {
|
|
47
|
+
try {
|
|
48
|
+
css.registerProperty({ name: m.name, syntax: '<time>', inherits: false, initialValue: '0s' });
|
|
49
|
+
registered.add(m.name);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error && error.name === 'InvalidModificationError')
|
|
53
|
+
registered.add(m.name);
|
|
54
|
+
else
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
43
58
|
return registered;
|
|
44
59
|
}
|
package/dist/schema.d.ts
CHANGED
package/dist/schema.js
CHANGED
|
@@ -20,7 +20,7 @@ export function validateSchema(schema) {
|
|
|
20
20
|
if (syntax.type === 'keyword') {
|
|
21
21
|
checkKeywords(spec.name, syntax.values);
|
|
22
22
|
}
|
|
23
|
-
else if (syntax.type !== 'color' && syntax.keywords) {
|
|
23
|
+
else if (syntax.type !== 'color' && syntax.type !== 'time' && syntax.keywords) {
|
|
24
24
|
// Numeric types may hold a keyword instead of a value ("<length> | auto");
|
|
25
25
|
// validate them the same way as the keyword type. Color keywords stay unvalidated.
|
|
26
26
|
checkKeywords(spec.name, syntax.keywords);
|
|
@@ -35,6 +35,8 @@ export function cssSyntax(syntax) {
|
|
|
35
35
|
return syntax.keywords && syntax.keywords.length ? `<length-percentage> | ${syntax.keywords.join(' | ')}` : '<length-percentage>';
|
|
36
36
|
case 'number':
|
|
37
37
|
return syntax.keywords && syntax.keywords.length ? `<number> | ${syntax.keywords.join(' | ')}` : '<number>';
|
|
38
|
+
case 'time':
|
|
39
|
+
return '<time>';
|
|
38
40
|
case 'color':
|
|
39
41
|
return syntax.keywords && syntax.keywords.length ? `<color> | ${syntax.keywords.join(' | ')}` : '<color>';
|
|
40
42
|
case 'keyword':
|
package/package.json
CHANGED
package/src/detector.ts
CHANGED
|
@@ -6,7 +6,10 @@ import type { PropertySpec } from './schema.js';
|
|
|
6
6
|
// - numeric values: folded into up to four weighted calc() hashes, placed in the x and y of
|
|
7
7
|
// transform-origin and perspective-origin (neither triggers layout), 1ms transitions;
|
|
8
8
|
// - colors: one real color property each, 1ms transitions;
|
|
9
|
-
// - keywords: an animation-name list with shadow-scoped empty @keyframes per allowed value
|
|
9
|
+
// - keywords: an animation-name list with shadow-scoped empty @keyframes per allowed value;
|
|
10
|
+
// - times: one registered <time> mirror each, 1ms transitions. A time cannot enter the numeric
|
|
11
|
+
// hash (calc() multiplies a time by numbers only), and only a registered custom property
|
|
12
|
+
// interpolates a time, so times are observed where CSS.registerProperty exists.
|
|
10
13
|
// Any transition or animation start marks the host as changed.
|
|
11
14
|
|
|
12
15
|
const HASH_SLOTS = 4;
|
|
@@ -69,6 +72,19 @@ export function keywordMirrors(schema: PropertySpec[]): KeywordMirror[] {
|
|
|
69
72
|
return out;
|
|
70
73
|
}
|
|
71
74
|
|
|
75
|
+
export interface TimeMirror {
|
|
76
|
+
// Name of the registered <time> mirror on the detector element, e.g. "--ax-typed-om-t0".
|
|
77
|
+
name: string;
|
|
78
|
+
// The source custom property the mirror reflects.
|
|
79
|
+
property: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// One registered <time> mirror per time spec, named by its position among the time specs. The
|
|
83
|
+
// definition is the same for every schema (<time>, not inherited, 0s), so schemas share the names.
|
|
84
|
+
export function timeMirrors(schema: PropertySpec[]): TimeMirror[] {
|
|
85
|
+
return schema.filter((spec) => spec.syntax.type === 'time').map((spec, i) => ({ name: `--${PREFIX}-t${i}`, property: spec.name }));
|
|
86
|
+
}
|
|
87
|
+
|
|
72
88
|
export interface DetectorTemplate {
|
|
73
89
|
css: string;
|
|
74
90
|
elementCount: number;
|
|
@@ -77,7 +93,7 @@ export interface DetectorTemplate {
|
|
|
77
93
|
// `registered` lists the properties registered by name. A registered non-inherited property
|
|
78
94
|
// does not reach the detector element inside the shadow root, so the host mirrors it into an
|
|
79
95
|
// unregistered (inherited) property. The slot resets the mirrors so author content does not
|
|
80
|
-
// inherit them.
|
|
96
|
+
// inherit them. Time specs are observed only when their time mirror is in `registered`.
|
|
81
97
|
export function buildDetector(schema: PropertySpec[], registered: Set<string> = new Set()): DetectorTemplate {
|
|
82
98
|
const mirrors: string[] = [];
|
|
83
99
|
const kwMirrors: string[] = [];
|
|
@@ -134,6 +150,13 @@ export function buildDetector(schema: PropertySpec[], registered: Set<string> =
|
|
|
134
150
|
});
|
|
135
151
|
});
|
|
136
152
|
|
|
153
|
+
for (const m of timeMirrors(schema)) {
|
|
154
|
+
if (!registered.has(m.name)) continue;
|
|
155
|
+
const spec = schema.filter((s) => s.name === m.property)[0];
|
|
156
|
+
elements[0].decls.push(`${m.name}: var(${source(spec)}, ${spec.initial});`);
|
|
157
|
+
elements[0].transitions.push(`${m.name} 1ms`);
|
|
158
|
+
}
|
|
159
|
+
|
|
137
160
|
const rules: string[] = [];
|
|
138
161
|
const keywords = schema.filter((s) => s.syntax.type === 'keyword');
|
|
139
162
|
if (keywords.length) {
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { StyleBinding, StyleSchema } from './binding.js';
|
|
2
2
|
export { attachDetector, buildDetector } from './detector.js';
|
|
3
3
|
export type { DetectorHandle, DetectorTemplate } from './detector.js';
|
|
4
|
-
export { parseColor, parseKeyword, parseLength, parseLengthPercentage, parseNumber } from './parse.js';
|
|
5
|
-
export { probeColor, probeLengthPercentage, probeNumber } from './probe.js';
|
|
4
|
+
export { parseColor, parseKeyword, parseLength, parseLengthPercentage, parseNumber, parseTime } from './parse.js';
|
|
5
|
+
export { probeColor, probeLengthPercentage, probeNumber, probeTime } from './probe.js';
|
|
6
6
|
export { StyleReader } from './reader.js';
|
|
7
7
|
export type { ReadStats } from './reader.js';
|
|
8
8
|
export { canRegister, registerProperties } from './register.js';
|
package/src/parse.ts
CHANGED
|
@@ -35,6 +35,17 @@ export function parseLength(text: string): number | null {
|
|
|
35
35
|
return m && m[2] === 'px' ? parseFloat(m[1]) : null;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
const TIME = new RegExp(`^(${NUMBER})(ms|s)$`);
|
|
39
|
+
|
|
40
|
+
// A <time> in ms. Engines serialize computed times in seconds ("0.05s"); an unregistered value
|
|
41
|
+
// comes back as written, in either unit. A bare number is not a time, as in CSS.
|
|
42
|
+
export function parseTime(text: string): number | null {
|
|
43
|
+
const m = TIME.exec(text);
|
|
44
|
+
if (!m) return null;
|
|
45
|
+
const v = parseFloat(m[1]);
|
|
46
|
+
return m[2] === 's' ? v * 1000 : v;
|
|
47
|
+
}
|
|
48
|
+
|
|
38
49
|
export function parseKeyword(text: string, values: string[]): string | null {
|
|
39
50
|
return values.indexOf(text) >= 0 ? text : null;
|
|
40
51
|
}
|
package/src/probe.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseColor } from './parse.js';
|
|
1
|
+
import { parseColor, parseTime } from './parse.js';
|
|
2
2
|
import type { LengthPercentage, Rgba } from './schema.js';
|
|
3
3
|
|
|
4
4
|
// Slow path for values the fast parsers cannot read (unregistered `calc(1em + 2px)`, named
|
|
@@ -48,6 +48,7 @@ function withProbe<T>(parent: Node, read: (p: Probe) => T): T {
|
|
|
48
48
|
probe.narrow.style.marginLeft = '';
|
|
49
49
|
probe.wide.style.marginLeft = '';
|
|
50
50
|
probe.narrow.style.color = '';
|
|
51
|
+
probe.narrow.style.transitionDelay = '';
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
@@ -77,3 +78,15 @@ export function probeColor(parent: Node, name: string): Rgba | null {
|
|
|
77
78
|
return text === sentinel ? null : parseColor(text);
|
|
78
79
|
});
|
|
79
80
|
}
|
|
81
|
+
|
|
82
|
+
// A time in ms, or null when the value is not a valid time. transition-delay is not inherited,
|
|
83
|
+
// so an invalid value would read as its initial 0s, the same as a real 0s. The second entry of
|
|
84
|
+
// the list tells them apart: an invalid var() invalidates the whole declaration, which leaves
|
|
85
|
+
// one entry instead of two. Negative delays are valid, so negative times read back too.
|
|
86
|
+
export function probeTime(parent: Node, name: string): number | null {
|
|
87
|
+
return withProbe(parent, (p) => {
|
|
88
|
+
p.narrow.style.transitionDelay = `var(${name}), 0s`;
|
|
89
|
+
const list = getComputedStyle(p.narrow).transitionDelay.split(',');
|
|
90
|
+
return list.length === 2 ? parseTime(list[0].trim()) : null;
|
|
91
|
+
});
|
|
92
|
+
}
|
package/src/reader.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { parseColor, parseKeyword, parseLength, parseLengthPercentage, parseNumber } from './parse.js';
|
|
2
|
-
import { probeColor, probeLengthPercentage, probeNumber } from './probe.js';
|
|
1
|
+
import { parseColor, parseKeyword, parseLength, parseLengthPercentage, parseNumber, parseTime } from './parse.js';
|
|
2
|
+
import { probeColor, probeLengthPercentage, probeNumber, probeTime } from './probe.js';
|
|
3
3
|
import type { PropertySpec, PropertyValue } from './schema.js';
|
|
4
4
|
|
|
5
5
|
export interface ReadStats {
|
|
@@ -62,6 +62,8 @@ function parseFast(spec: PropertySpec, text: string): PropertyValue {
|
|
|
62
62
|
case 'number':
|
|
63
63
|
if (spec.syntax.keywords && spec.syntax.keywords.indexOf(text) >= 0) return text;
|
|
64
64
|
return parseNumber(text);
|
|
65
|
+
case 'time':
|
|
66
|
+
return parseTime(text);
|
|
65
67
|
case 'color':
|
|
66
68
|
if (spec.syntax.keywords && spec.syntax.keywords.indexOf(text) >= 0) return text;
|
|
67
69
|
return parseColor(text);
|
|
@@ -78,6 +80,8 @@ function parseSlow(spec: PropertySpec, parent: Node): PropertyValue {
|
|
|
78
80
|
return probeLengthPercentage(parent, spec.name);
|
|
79
81
|
case 'number':
|
|
80
82
|
return probeNumber(parent, spec.name);
|
|
83
|
+
case 'time':
|
|
84
|
+
return probeTime(parent, spec.name);
|
|
81
85
|
case 'color':
|
|
82
86
|
return probeColor(parent, spec.name);
|
|
83
87
|
case 'keyword':
|
package/src/register.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { keywordMirrors } from './detector.js';
|
|
1
|
+
import { keywordMirrors, timeMirrors } from './detector.js';
|
|
2
2
|
import { cssSyntax } from './schema.js';
|
|
3
3
|
import type { PropertySpec } from './schema.js';
|
|
4
4
|
|
|
@@ -17,7 +17,8 @@ export function canRegister(): boolean {
|
|
|
17
17
|
|
|
18
18
|
// Registers the properties that ask for it. Returns the names that are registered after the
|
|
19
19
|
// call, including ones another copy of the package registered before (first registration
|
|
20
|
-
// wins, so definitions must never change between versions)
|
|
20
|
+
// wins, so definitions must never change between versions), and the time mirrors of the
|
|
21
|
+
// detector (see timeMirrors).
|
|
21
22
|
export function registerProperties(schema: PropertySpec[]): Set<string> {
|
|
22
23
|
const registered = new Set<string>();
|
|
23
24
|
if (!canRegister()) return registered;
|
|
@@ -45,5 +46,16 @@ export function registerProperties(schema: PropertySpec[]): Set<string> {
|
|
|
45
46
|
if (!error || (error as Error).name !== 'InvalidModificationError') throw error;
|
|
46
47
|
}
|
|
47
48
|
}
|
|
49
|
+
// Time specs get a registered <time> mirror on the detector element, the only way to make a
|
|
50
|
+
// time transition (and so be observed). Same definition for every schema, like the above.
|
|
51
|
+
for (const m of timeMirrors(schema)) {
|
|
52
|
+
try {
|
|
53
|
+
css.registerProperty!({ name: m.name, syntax: '<time>', inherits: false, initialValue: '0s' });
|
|
54
|
+
registered.add(m.name);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error && (error as Error).name === 'InvalidModificationError') registered.add(m.name);
|
|
57
|
+
else throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
48
60
|
return registered;
|
|
49
61
|
}
|
package/src/schema.ts
CHANGED
|
@@ -3,6 +3,8 @@ export type PropertySyntax =
|
|
|
3
3
|
| { type: 'length'; keywords?: string[] }
|
|
4
4
|
| { type: 'length-percentage'; keywords?: string[] }
|
|
5
5
|
| { type: 'number'; keywords?: string[] }
|
|
6
|
+
// A CSS <time>, read as a number of milliseconds ("0.2s" and "200ms" are both 200).
|
|
7
|
+
| { type: 'time' }
|
|
6
8
|
// `keywords`: extra identifiers the property may hold instead of a color (returned as strings).
|
|
7
9
|
| { type: 'color'; keywords?: string[] }
|
|
8
10
|
// Allowed values. CSS-wide keywords are rejected. `none` is allowed for existing APIs, but
|
|
@@ -55,7 +57,7 @@ export function validateSchema(schema: PropertySpec[]): void {
|
|
|
55
57
|
const syntax = spec.syntax;
|
|
56
58
|
if (syntax.type === 'keyword') {
|
|
57
59
|
checkKeywords(spec.name, syntax.values);
|
|
58
|
-
} else if (syntax.type !== 'color' && syntax.keywords) {
|
|
60
|
+
} else if (syntax.type !== 'color' && syntax.type !== 'time' && syntax.keywords) {
|
|
59
61
|
// Numeric types may hold a keyword instead of a value ("<length> | auto");
|
|
60
62
|
// validate them the same way as the keyword type. Color keywords stay unvalidated.
|
|
61
63
|
checkKeywords(spec.name, syntax.keywords);
|
|
@@ -71,6 +73,8 @@ export function cssSyntax(syntax: PropertySyntax): string {
|
|
|
71
73
|
return syntax.keywords && syntax.keywords.length ? `<length-percentage> | ${syntax.keywords.join(' | ')}` : '<length-percentage>';
|
|
72
74
|
case 'number':
|
|
73
75
|
return syntax.keywords && syntax.keywords.length ? `<number> | ${syntax.keywords.join(' | ')}` : '<number>';
|
|
76
|
+
case 'time':
|
|
77
|
+
return '<time>';
|
|
74
78
|
case 'color':
|
|
75
79
|
return syntax.keywords && syntax.keywords.length ? `<color> | ${syntax.keywords.join(' | ')}` : '<color>';
|
|
76
80
|
case 'keyword':
|