@rak200/ui 0.2.4 → 0.2.6

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 CHANGED
@@ -64,8 +64,8 @@ components; `docs/` is what describes them.
64
64
 
65
65
  ## Status
66
66
 
67
- **v0.** One component and the token layer, built to the ecosystem's full quality bar rather than
68
- sketched: type-checked at the strictest available setting, formatted, tested in a real browser
69
- and **asserted against axe** for WCAG A/AA, 100% coverage and **100% mutation score**, scanned, and
70
- every public symbol documented. The v0
71
- surface in RFC 0016 grows from here — see [ROADMAP.md](ROADMAP.md).
67
+ **v0.** Two components — `<ui-button>` and `<ui-field>` — and the token layer under them, built to
68
+ the ecosystem's full quality bar rather than sketched: type-checked at the strictest available
69
+ setting, formatted, tested in a real browser and **asserted against axe** for WCAG A/AA, 100%
70
+ coverage and **100% mutation score**, scanned, and every public symbol documented. The v0 surface in
71
+ RFC 0016 grows from here — see [ROADMAP.md](ROADMAP.md).
@@ -1 +1 @@
1
- {"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../src/button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAEjE,+CAA+C;AAC/C,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,WAAW,CAAC;AAEpD;;;;;;;;;;;;;GAaG;AACH,qBAAa,QAAS,SAAQ,UAAU;IACpC,gBAAyB,MAAM,0BAoC7B;IAEF,gBAAyB,UAAU;;;;;;;;;MAGjC;IAEF;;;;;;;;OAQG;IACH,OAAO,EAAE,aAAa,CAAa;IAEnC,kFAAkF;IAClF,QAAQ,UAAS;IAER,MAAM,IAAI,cAAc;CAOpC;AAQD,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,WAAW,EAAE,QAAQ,CAAC;KACzB;CACJ"}
1
+ {"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../src/button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAGjE,+CAA+C;AAC/C,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,WAAW,CAAC;AAEpD;;;;;;;;;;;;;GAaG;AACH,qBAAa,QAAS,SAAQ,UAAU;IACpC,gBAAyB,MAAM,0BA6E7B;IAEF,gBAAyB,UAAU;;;;;;;;;MAGjC;IAEF;;;;;;;;OAQG;IACH,OAAO,EAAE,aAAa,CAAa;IAEnC,kFAAkF;IAClF,QAAQ,UAAS;IAER,MAAM,IAAI,cAAc;CAOpC;AAQD,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,WAAW,EAAE,QAAQ,CAAC;KACzB;CACJ"}
package/dist/button.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { LitElement, css, html } from 'lit';
2
+ import { reference } from './reference.js';
2
3
  /**
3
4
  * A button.
4
5
  *
@@ -36,11 +37,25 @@ export class UiButton extends LitElement {
36
37
 
37
38
  button {
38
39
  font: inherit;
39
- font-family: var(--ui-font, system-ui, sans-serif);
40
+ font-family: ${reference('--ui-font')};
40
41
  border: 1px solid transparent;
41
- border-radius: var(--ui-radius, 0.375rem);
42
- padding: var(--ui-space, 0.5rem) calc(var(--ui-space, 0.5rem) * 2);
42
+ border-radius: ${reference('--ui-radius')};
43
+ padding: ${reference('--ui-space')} calc(${reference('--ui-space')} * 2);
43
44
  cursor: pointer;
45
+ /* Measured in WebKit and in Chromium under a phone viewport: the platform
46
+ paints its own wash on tap — 40% black in WebKit, the Android blue in
47
+ Chromium — over whatever the component decided, so the pressed colour below
48
+ arrives underneath it and is not what a finger sees.
49
+
50
+ Removing it is only correct because the pressed state exists. Until it did,
51
+ this wash was the ONLY response a touch got, and turning it off would have
52
+ left a button that answers a finger with nothing. */
53
+ -webkit-tap-highlight-color: transparent;
54
+ /* Only the colour moves. The focus ring is deliberately not in this list:
55
+ delaying the affordance that says *this is where you are* is the opposite of
56
+ what it exists to do. */
57
+ transition: background-color ${reference('--ui-duration-state')}
58
+ ${reference('--ui-easing-state')};
44
59
  }
45
60
 
46
61
  button:disabled {
@@ -51,20 +66,47 @@ export class UiButton extends LitElement {
51
66
  /* A visible focus ring is not decoration: removing it is the single most common
52
67
  way a component stops being usable by keyboard. */
53
68
  button:focus-visible {
54
- outline: 2px solid var(--ui-color-focus, #b45309);
69
+ outline: 2px solid ${reference('--ui-color-focus')};
55
70
  outline-offset: 2px;
56
71
  }
57
72
 
58
73
  button.primary {
59
- background: var(--ui-color-accent, #2563eb);
60
- color: var(--ui-color-accent-contrast, #ffffff);
74
+ background: ${reference('--ui-color-accent')};
75
+ color: ${reference('--ui-color-accent-contrast')};
61
76
  }
62
77
 
63
78
  button.secondary {
64
- background: var(--ui-color-surface, #ffffff);
65
- color: var(--ui-color-text, #1f2937);
79
+ background: ${reference('--ui-color-surface')};
80
+ color: ${reference('--ui-color-text')};
66
81
  border-color: currentcolor;
67
82
  }
83
+
84
+ /* The :not(:disabled) guard is measured rather than assumed: a disabled button
85
+ still matches :hover and :active, so without it the button would light up under a
86
+ pointer that cannot activate it. Ordering does not substitute for the guard — both
87
+ rules below outrank the resting one on specificity whatever their position. */
88
+ button.primary:not(:disabled):hover {
89
+ background: ${reference('--ui-color-accent-hover')};
90
+ }
91
+
92
+ button.secondary:not(:disabled):hover {
93
+ background: ${reference('--ui-color-hover')};
94
+ }
95
+
96
+ /* A press is over in about 100ms, so an entering transition of 150ms would land
97
+ after the finger has left and the pressed colour would never be seen. Zero here
98
+ rather than a second token: it is a fact about how long a click lasts, not a
99
+ decision a host would want to retune. And the pressed state is never the only
100
+ feedback a component gives — activating by Enter produces no :active at all. */
101
+ button.primary:not(:disabled):active {
102
+ background: ${reference('--ui-color-accent-pressed')};
103
+ transition-duration: 0s;
104
+ }
105
+
106
+ button.secondary:not(:disabled):active {
107
+ background: ${reference('--ui-color-pressed')};
108
+ transition-duration: 0s;
109
+ }
68
110
  `; }
69
111
  static { this.properties = {
70
112
  variant: { type: String, reflect: true },
@@ -1 +1 @@
1
- {"version":3,"file":"button.js","sourceRoot":"","sources":["../src/button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAuB,MAAM,KAAK,CAAC;AAKjE;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,QAAS,SAAQ,UAAU;IAAxC;;QA4CI;;;;;;;;WAQG;QACH,YAAO,GAAkB,SAAS,CAAC;QAEnC,kFAAkF;QAClF,aAAQ,GAAG,KAAK,CAAC;IASrB,CAAC;aAhE4B,WAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAoCpC,AApC8B,CAoC7B;aAEuB,eAAU,GAAG;QAClC,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QACxC,QAAQ,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE;KAC7C,AAHkC,CAGjC;IAgBO,MAAM;QACX,OAAO,IAAI,CAAA;4BACS,IAAI,CAAC,OAAO,cAAc,IAAI,CAAC,QAAQ;;;SAG1D,CAAC;IACN,CAAC;;AAGL,kFAAkF;AAClF,wFAAwF;AACxF,yFAAyF;AACzF,4EAA4E;AAC5E,cAAc,CAAC,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC"}
1
+ {"version":3,"file":"button.js","sourceRoot":"","sources":["../src/button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAuB,MAAM,KAAK,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAK3C;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,QAAS,SAAQ,UAAU;IAAxC;;QAqFI;;;;;;;;WAQG;QACH,YAAO,GAAkB,SAAS,CAAC;QAEnC,kFAAkF;QAClF,aAAQ,GAAG,KAAK,CAAC;IASrB,CAAC;aAzG4B,WAAM,GAAG,GAAG,CAAA;;;;;;;2BAOd,SAAS,CAAC,WAAW,CAAC;;6BAEpB,SAAS,CAAC,aAAa,CAAC;uBAC9B,SAAS,CAAC,YAAY,CAAC,SAAS,SAAS,CAAC,YAAY,CAAC;;;;;;;;;;;;;;2CAcnC,SAAS,CAAC,qBAAqB,CAAC;kBACzD,SAAS,CAAC,mBAAmB,CAAC;;;;;;;;;;;iCAWf,SAAS,CAAC,kBAAkB,CAAC;;;;;0BAKpC,SAAS,CAAC,mBAAmB,CAAC;qBACnC,SAAS,CAAC,4BAA4B,CAAC;;;;0BAIlC,SAAS,CAAC,oBAAoB,CAAC;qBACpC,SAAS,CAAC,iBAAiB,CAAC;;;;;;;;;0BASvB,SAAS,CAAC,yBAAyB,CAAC;;;;0BAIpC,SAAS,CAAC,kBAAkB,CAAC;;;;;;;;;0BAS7B,SAAS,CAAC,2BAA2B,CAAC;;;;;0BAKtC,SAAS,CAAC,oBAAoB,CAAC;;;KAGpD,AA7E8B,CA6E7B;aAEuB,eAAU,GAAG;QAClC,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;QACxC,QAAQ,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE;KAC7C,AAHkC,CAGjC;IAgBO,MAAM;QACX,OAAO,IAAI,CAAA;4BACS,IAAI,CAAC,OAAO,cAAc,IAAI,CAAC,QAAQ;;;SAG1D,CAAC;IACN,CAAC;;AAGL,kFAAkF;AAClF,wFAAwF;AACxF,yFAAyF;AACzF,4EAA4E;AAC5E,cAAc,CAAC,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../src/field.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAKjE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,OAAQ,SAAQ,UAAU;;IACnC,gBAAyB,MAAM,0BAuB7B;IAeO,iBAAiB,IAAI,IAAI;IASzB,oBAAoB,IAAI,IAAI;IAK5B,YAAY,IAAI,IAAI;IAIpB,MAAM,IAAI,cAAc;CAyFpC;AAQD,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,UAAU,EAAE,OAAO,CAAC;KACvB;CACJ"}
1
+ {"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../src/field.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAMjE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,OAAQ,SAAQ,UAAU;;IACnC,gBAAyB,MAAM,0BAuB7B;IAeO,iBAAiB,IAAI,IAAI;IASzB,oBAAoB,IAAI,IAAI;IAK5B,YAAY,IAAI,IAAI;IAIpB,MAAM,IAAI,cAAc;CAyFpC;AAQD,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,UAAU,EAAE,OAAO,CAAC;KACvB;CACJ"}
package/dist/field.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { LitElement, css, html } from 'lit';
2
+ import { reference } from './reference.js';
2
3
  /** Distinguishes one field's generated ids from another's. */
3
4
  let sequence = 0;
4
5
  /**
@@ -29,24 +30,24 @@ export class UiField extends LitElement {
29
30
  static { this.styles = css `
30
31
  :host {
31
32
  display: block;
32
- font-family: var(--ui-font, system-ui, sans-serif);
33
+ font-family: ${reference('--ui-font')};
33
34
  }
34
35
 
35
36
  .stack {
36
37
  display: flex;
37
38
  flex-direction: column;
38
- gap: calc(var(--ui-space, 0.5rem) / 2);
39
+ gap: calc(${reference('--ui-space')} / 2);
39
40
  }
40
41
 
41
42
  slot[name='help']::slotted(*) {
42
- color: var(--ui-color-text, #1f2937);
43
+ color: ${reference('--ui-color-text')};
43
44
  font-size: 0.875em;
44
45
  }
45
46
 
46
47
  /* Colour is not the only cue — the error text says what is wrong, and
47
48
  aria-invalid marks the control regardless of styling. */
48
49
  slot[name='error']::slotted(*) {
49
- color: var(--ui-color-danger, #b91c1c);
50
+ color: ${reference('--ui-color-danger')};
50
51
  font-size: 0.875em;
51
52
  }
52
53
  `; }
package/dist/field.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"field.js","sourceRoot":"","sources":["../src/field.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"field.js","sourceRoot":"","sources":["../src/field.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { UiButton, type ButtonVariant } from './button.js';
2
2
  export { UiField } from './field.js';
3
- export { tokens, defaults, darkScheme, tokenStyleSheet, type Token } from './tokens.js';
3
+ export { tokens, derivedTokens, defaults, formulas, darkScheme, tokenStyleSheet, type Token, type DerivedToken, } from './tokens.js';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,eAAe,EAAE,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EACH,MAAM,EACN,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,eAAe,EACf,KAAK,KAAK,EACV,KAAK,YAAY,GACpB,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { UiButton } from './button.js';
2
2
  export { UiField } from './field.js';
3
- export { tokens, defaults, darkScheme, tokenStyleSheet } from './tokens.js';
3
+ export { tokens, derivedTokens, defaults, formulas, darkScheme, tokenStyleSheet, } from './tokens.js';
4
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAsB,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,eAAe,EAAc,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAsB,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EACH,MAAM,EACN,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,eAAe,GAGlB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * How a component writes a token, and the only way one should.
3
+ *
4
+ * Every `var(--ui-*, fallback)` used to carry a hand-copied literal — thirteen of them
5
+ * across two components, all agreeing, none compared to anything. A fallback only shows
6
+ * when the `:root` block is absent, so the first copy to disagree would have done it
7
+ * invisibly. Generated from `defaults` and `formulas`, a fallback stops being a value that
8
+ * *can* drift and becomes one that cannot.
9
+ *
10
+ * **For a derived name the fallback is the formula, and that placement is the whole of
11
+ * RFC 0002's item 1.** A derivation resolves where it is *used*, against the grounds in
12
+ * force at that element — so a component in a dark subtree, or under a second theme, mixes
13
+ * against that subtree's grounds without either being restated. Declared beside the
14
+ * grounds instead, it would resolve once at `:root` and freeze; `src/tokens.ts` carries
15
+ * that measurement beside {@link formulas}.
16
+ *
17
+ * **This module exists because `src/tokens.ts` imports no Lit, and must not start.** That
18
+ * module is what a native shell reads, and a target that is not the web cannot take a
19
+ * dependency on a web renderer to find out what a colour is. `unsafeCSS` is what forces
20
+ * the split, and it is safe here in the only sense that matters: every argument it ever
21
+ * receives is this package's own literal, from a record the compiler keeps exhaustive.
22
+ *
23
+ * Internal — it is not re-exported from `src/index.ts`, because what a consumer overrides
24
+ * is the token, never the reference to it.
25
+ */
26
+ import { type CSSResult } from 'lit';
27
+ import { type DerivedToken, type Token } from './tokens.js';
28
+ /**
29
+ * A token as a component reads it: the custom property, with the package's own value
30
+ * behind it.
31
+ *
32
+ * ```ts
33
+ * css`button { border-radius: ${reference('--ui-radius')}; }`;
34
+ * // → button { border-radius: var(--ui-radius, 0.375rem); }
35
+ * ```
36
+ *
37
+ * @param name - A ground or derived token. The fallback is that token's default where it
38
+ * has one and its formula where it does not.
39
+ */
40
+ export declare function reference(name: Token | DerivedToken): CSSResult;
41
+ //# sourceMappingURL=reference.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reference.d.ts","sourceRoot":"","sources":["../src/reference.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,KAAK,CAAC;AAChD,OAAO,EAAqC,KAAK,YAAY,EAAE,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAO/F;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,KAAK,GAAG,YAAY,GAAG,SAAS,CAE/D"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * How a component writes a token, and the only way one should.
3
+ *
4
+ * Every `var(--ui-*, fallback)` used to carry a hand-copied literal — thirteen of them
5
+ * across two components, all agreeing, none compared to anything. A fallback only shows
6
+ * when the `:root` block is absent, so the first copy to disagree would have done it
7
+ * invisibly. Generated from `defaults` and `formulas`, a fallback stops being a value that
8
+ * *can* drift and becomes one that cannot.
9
+ *
10
+ * **For a derived name the fallback is the formula, and that placement is the whole of
11
+ * RFC 0002's item 1.** A derivation resolves where it is *used*, against the grounds in
12
+ * force at that element — so a component in a dark subtree, or under a second theme, mixes
13
+ * against that subtree's grounds without either being restated. Declared beside the
14
+ * grounds instead, it would resolve once at `:root` and freeze; `src/tokens.ts` carries
15
+ * that measurement beside {@link formulas}.
16
+ *
17
+ * **This module exists because `src/tokens.ts` imports no Lit, and must not start.** That
18
+ * module is what a native shell reads, and a target that is not the web cannot take a
19
+ * dependency on a web renderer to find out what a colour is. `unsafeCSS` is what forces
20
+ * the split, and it is safe here in the only sense that matters: every argument it ever
21
+ * receives is this package's own literal, from a record the compiler keeps exhaustive.
22
+ *
23
+ * Internal — it is not re-exported from `src/index.ts`, because what a consumer overrides
24
+ * is the token, never the reference to it.
25
+ */
26
+ import { unsafeCSS } from 'lit';
27
+ import { defaults, derivedTokens, formulas } from './tokens.js';
28
+ /** Whether a name is computed rather than declared, which decides where its fallback comes from. */
29
+ function isDerived(name) {
30
+ return derivedTokens.includes(name);
31
+ }
32
+ /**
33
+ * A token as a component reads it: the custom property, with the package's own value
34
+ * behind it.
35
+ *
36
+ * ```ts
37
+ * css`button { border-radius: ${reference('--ui-radius')}; }`;
38
+ * // → button { border-radius: var(--ui-radius, 0.375rem); }
39
+ * ```
40
+ *
41
+ * @param name - A ground or derived token. The fallback is that token's default where it
42
+ * has one and its formula where it does not.
43
+ */
44
+ export function reference(name) {
45
+ return unsafeCSS(`var(${name}, ${isDerived(name) ? formulas[name] : defaults[name]})`);
46
+ }
47
+ //# sourceMappingURL=reference.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reference.js","sourceRoot":"","sources":["../src/reference.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,SAAS,EAAkB,MAAM,KAAK,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAiC,MAAM,aAAa,CAAC;AAE/F,oGAAoG;AACpG,SAAS,SAAS,CAAC,IAA0B;IACzC,OAAQ,aAAmC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CAAC,IAA0B;IAChD,OAAO,SAAS,CAAC,OAAO,IAAI,KAAK,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3F,CAAC"}
package/dist/tokens.d.ts CHANGED
@@ -8,18 +8,79 @@
8
8
  *
9
9
  * Every token is a CSS custom property under `--ui-`, so a host overrides one by
10
10
  * setting it anywhere above the component — no build step, no theme object, no fork.
11
+ *
12
+ * **The exported set splits in two, and the split is the shape of RFC 0002.** {@link tokens}
13
+ * are *ground*: they carry a literal default and are emitted at `:root`.
14
+ * {@link derivedTokens} are computed from the grounds by a {@link formulas | formula} and
15
+ * are **never** emitted there — a derivation declared beside its grounds resolves once and
16
+ * freezes, which is measured rather than feared.
17
+ */
18
+ /**
19
+ * The token names this package defines, as they appear in CSS — the *ground* half: the
20
+ * names that have a default and are emitted at `:root`.
21
+ *
22
+ * It is not called `groundTokens`, and that is a cost rather than an oversight: renaming
23
+ * an exported name is breaking, and Layer 1 would spend a major on it. So the pair reads
24
+ * asymmetrically and this one carries the documented meaning *the names that have a
25
+ * default*.
26
+ *
27
+ * **The set is open, and that is a promise rather than an accident.** It grows as
28
+ * components arrive — a category enters with the pull request of the component that
29
+ * consumes it — so asserting completeness over it asserts something this package does not
30
+ * offer. Adding a name is a `feat`, never a break, and it costs nothing at runtime; what
31
+ * it does break is code that **enumerates** the set, an exhaustive `Record<Token, string>`
32
+ * above all. The supported shape is the partial map, which is what a theme is anyway.
11
33
  */
12
- /** The token names this package defines, as they appear in CSS. */
13
- export declare const tokens: readonly ["--ui-color-accent", "--ui-color-accent-contrast", "--ui-color-surface", "--ui-color-text", "--ui-color-focus", "--ui-color-danger", "--ui-radius", "--ui-space", "--ui-font"];
14
- /** A CSS custom property this package defines. */
34
+ export declare const tokens: readonly ["--ui-color-accent", "--ui-color-accent-contrast", "--ui-color-surface", "--ui-color-text", "--ui-color-focus", "--ui-color-danger", "--ui-radius", "--ui-space", "--ui-font", "--ui-duration-100", "--ui-easing-state"];
35
+ /** A CSS custom property this package defines and gives a default. */
15
36
  export type Token = (typeof tokens)[number];
16
37
  /**
17
- * The default value of every token, applied at `:root` by {@link tokenStyleSheet}.
38
+ * The roles computed from the grounds rather than declared beside them.
39
+ *
40
+ * **These are write-only.** A host may override one, and every component picks the
41
+ * override up; nothing can read one back, because a derived name has no declared value —
42
+ * only a {@link formulas | formula} that lives in the `var()` fallback at the point of
43
+ * use. That is the one cost of the derivation that a consumer can be surprised by, and
44
+ * what it buys is an override surface a host can hold in their head: change the accent and
45
+ * the hover and pressed colours follow, rather than being one more name each.
46
+ */
47
+ export declare const derivedTokens: readonly ["--ui-duration-state", "--ui-color-hover", "--ui-color-pressed", "--ui-color-accent-hover", "--ui-color-accent-pressed"];
48
+ /** A CSS custom property this package computes rather than declares. */
49
+ export type DerivedToken = (typeof derivedTokens)[number];
50
+ /**
51
+ * The default value of every ground token, applied at `:root` by {@link tokenStyleSheet}.
18
52
  *
19
53
  * These are deliberately plain and low-contrast-safe rather than branded: a design
20
54
  * system's defaults are what a host sees before it has decided anything.
21
55
  */
22
56
  export declare const defaults: Readonly<Record<Token, string>>;
57
+ /**
58
+ * How each derived role computes when the host has not set it.
59
+ *
60
+ * **Never emitted at `:root`, and this is the one measured failure of the whole design
61
+ * rather than a caution:**
62
+ *
63
+ * ```css
64
+ * :root {
65
+ * --ui-color-hover: color-mix(in oklab, var(--ui-color-text) 8%, var(--ui-color-surface));
66
+ * }
67
+ * ```
68
+ *
69
+ * That resolves *once*, against the grounds in force at `:root`, and freezes. A dark
70
+ * subtree then inherits the light mix — a near-white hover on charcoal — with nothing to
71
+ * read anywhere. The formula belongs in the `var()` fallback at the point of use, where it
72
+ * resolves against the grounds in force *there*, which is what makes a derived role follow
73
+ * a theme and a scheme without being restated in either. `src/reference.ts` is what writes
74
+ * it, and `tests/tokens.test.ts` gates the rule rather than trusting this comment.
75
+ *
76
+ * Composed rather than written out, because a formula is data about *which* grounds a role
77
+ * mixes and in what proportion — and a hand-written string can name a token that does not
78
+ * exist, or forget the default above, and CSS reports either by rendering nothing.
79
+ *
80
+ * Exported because a target that is not CSS cannot evaluate `color-mix()` and has to
81
+ * resolve these itself, frozen per theme, from the same source the components read.
82
+ */
83
+ export declare const formulas: Readonly<Record<DerivedToken, string>>;
23
84
  /**
24
85
  * The grounds whose value differs when the page is rendered dark.
25
86
  *
@@ -42,7 +103,8 @@ export declare const defaults: Readonly<Record<Token, string>>;
42
103
  export declare const darkScheme: Readonly<Partial<Record<Token, string>>>;
43
104
  /**
44
105
  * The token defaults as a CSS rule, for a host that wants them without importing a
45
- * component. Returns the text of a `:root` block; a host inserts it however it prefers.
106
+ * component. Returns the text of a `:root` block and the reduced-motion rule beside it; a
107
+ * host inserts them however it prefers.
46
108
  *
47
109
  * **It declares `color-scheme` as well as the tokens, and that is deliberate.**
48
110
  * `color-scheme` is a real property rather than a custom one, so it can never be a token —
@@ -50,6 +112,16 @@ export declare const darkScheme: Readonly<Partial<Record<Token, string>>>;
50
112
  * host would have to remember, and forgetting means dark mode simply never happens, with
51
113
  * no error anywhere to read. A host who wants something else — `only light`, say — governs
52
114
  * the order this sheet is inserted in, which is a knob they already hold.
115
+ *
116
+ * **Reduced motion is honoured here, once, rather than in each component.** A component
117
+ * reads `--ui-duration-state` and never learns why it changed, which is the argument for
118
+ * motion being tokens rather than literals: a hardcoded `150ms` is not merely
119
+ * un-overridable, it is an accessibility defect every component would have to fix on its
120
+ * own. The block declares the *derived* duration names as well as the ground ones — the
121
+ * only place either may appear at `:root`, and legal there precisely because what it
122
+ * declares is a literal rather than a formula. Without it, a host who tuned
123
+ * `--ui-duration-state` would keep their motion through the collapse, and the setting
124
+ * would be honoured for everyone except the people who had touched it.
53
125
  */
54
126
  export declare function tokenStyleSheet(): string;
55
127
  //# sourceMappingURL=tokens.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,mEAAmE;AACnE,eAAO,MAAM,MAAM,0LAUT,CAAC;AAEX,kDAAkD;AAClD,MAAM,MAAM,KAAK,GAAG,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5C;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAkBpD,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,UAAU,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAW/D,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAUxC"}
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,MAAM,oOAqBT,CAAC;AAEX,sEAAsE;AACtE,MAAM,MAAM,KAAK,GAAG,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5C;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,oIAShB,CAAC;AAEX,wEAAwE;AACxE,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CA6BpD,CAAC;AAqBF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAY3D,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,UAAU,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAW/D,CAAC;AAKF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAsBxC"}
package/dist/tokens.js CHANGED
@@ -8,8 +8,29 @@
8
8
  *
9
9
  * Every token is a CSS custom property under `--ui-`, so a host overrides one by
10
10
  * setting it anywhere above the component — no build step, no theme object, no fork.
11
+ *
12
+ * **The exported set splits in two, and the split is the shape of RFC 0002.** {@link tokens}
13
+ * are *ground*: they carry a literal default and are emitted at `:root`.
14
+ * {@link derivedTokens} are computed from the grounds by a {@link formulas | formula} and
15
+ * are **never** emitted there — a derivation declared beside its grounds resolves once and
16
+ * freezes, which is measured rather than feared.
17
+ */
18
+ /**
19
+ * The token names this package defines, as they appear in CSS — the *ground* half: the
20
+ * names that have a default and are emitted at `:root`.
21
+ *
22
+ * It is not called `groundTokens`, and that is a cost rather than an oversight: renaming
23
+ * an exported name is breaking, and Layer 1 would spend a major on it. So the pair reads
24
+ * asymmetrically and this one carries the documented meaning *the names that have a
25
+ * default*.
26
+ *
27
+ * **The set is open, and that is a promise rather than an accident.** It grows as
28
+ * components arrive — a category enters with the pull request of the component that
29
+ * consumes it — so asserting completeness over it asserts something this package does not
30
+ * offer. Adding a name is a `feat`, never a break, and it costs nothing at runtime; what
31
+ * it does break is code that **enumerates** the set, an exhaustive `Record<Token, string>`
32
+ * above all. The supported shape is the partial map, which is what a theme is anyway.
11
33
  */
12
- /** The token names this package defines, as they appear in CSS. */
13
34
  export const tokens = [
14
35
  '--ui-color-accent',
15
36
  '--ui-color-accent-contrast',
@@ -20,9 +41,40 @@ export const tokens = [
20
41
  '--ui-radius',
21
42
  '--ui-space',
22
43
  '--ui-font',
44
+ // Motion, and it arrives with the component that consumes it rather than with the
45
+ // twelve that might: `ui-button` accepts interaction and until now showed no feedback
46
+ // for it, which is a defect rather than a gap.
47
+ //
48
+ // The duration is a *step* in a scale, named by an ordinal with gaps so that inserting
49
+ // `--ui-duration-150` later is additive rather than a rename. No component reads it —
50
+ // they read `--ui-duration-state`, which points in here. Easing has no scale under it,
51
+ // because a curve is qualitative rather than relative, so its purpose name *is* the
52
+ // ground and a component reads it directly.
53
+ '--ui-duration-100',
54
+ '--ui-easing-state',
55
+ ];
56
+ /**
57
+ * The roles computed from the grounds rather than declared beside them.
58
+ *
59
+ * **These are write-only.** A host may override one, and every component picks the
60
+ * override up; nothing can read one back, because a derived name has no declared value —
61
+ * only a {@link formulas | formula} that lives in the `var()` fallback at the point of
62
+ * use. That is the one cost of the derivation that a consumer can be surprised by, and
63
+ * what it buys is an override surface a host can hold in their head: change the accent and
64
+ * the hover and pressed colours follow, rather than being one more name each.
65
+ */
66
+ export const derivedTokens = [
67
+ '--ui-duration-state',
68
+ // `hover` matches the pseudo-class it answers to; `pressed` deliberately does not —
69
+ // `--ui-color-active` would read as *the active item* as readily as *the pressed
70
+ // control*, and the role this implements was named `accent hover / pressed`.
71
+ '--ui-color-hover',
72
+ '--ui-color-pressed',
73
+ '--ui-color-accent-hover',
74
+ '--ui-color-accent-pressed',
23
75
  ];
24
76
  /**
25
- * The default value of every token, applied at `:root` by {@link tokenStyleSheet}.
77
+ * The default value of every ground token, applied at `:root` by {@link tokenStyleSheet}.
26
78
  *
27
79
  * These are deliberately plain and low-contrast-safe rather than branded: a design
28
80
  * system's defaults are what a host sees before it has decided anything.
@@ -45,6 +97,73 @@ export const defaults = {
45
97
  '--ui-radius': '0.375rem',
46
98
  '--ui-space': '0.5rem',
47
99
  '--ui-font': 'system-ui, sans-serif',
100
+ // The ordinal is a position, not a millisecond count, and the value is chosen so that
101
+ // the two cannot be confused: `--ui-duration-100: 100ms` would teach a reader an
102
+ // arithmetic that breaks the moment a second step is anything but 200ms.
103
+ '--ui-duration-100': '150ms',
104
+ // Symmetric, because a state transition reverses mid-flight: the pointer leaves a
105
+ // button while the hover is still arriving, and an asymmetric curve makes the return
106
+ // trip visibly different from the outbound one. The keyword rather than the
107
+ // `cubic-bezier` it stands for — nothing here needs a curve the platform has no name
108
+ // for. `enter` will want an ease-out and `exit` an ease-in, and they arrive with the
109
+ // overlays that have an enter and an exit to name.
110
+ '--ui-easing-state': 'ease-in-out',
111
+ };
112
+ /**
113
+ * A ground as it is written inside a formula: the name, with its own default behind it.
114
+ *
115
+ * **The default is not decoration.** A formula only ever runs as the fallback of a name
116
+ * nobody declared, which is precisely the page that inserted no `:root` block — and a bare
117
+ * `var(--ui-color-text)` there is invalid at computed-value time, which takes the whole
118
+ * `color-mix()` down with it and leaves the declaration unset. Measured, as a transparent
119
+ * hover on a page that had declared nothing. So a derivation carries its grounds' defaults
120
+ * exactly the way a component carries them.
121
+ */
122
+ function ground(name) {
123
+ return `var(${name}, ${defaults[name]})`;
124
+ }
125
+ /** `amount`% of `foreground` mixed into `background`, in a perceptual space. */
126
+ function mix(foreground, amount, background) {
127
+ return `color-mix(in oklab, ${ground(foreground)} ${String(amount)}%, ${ground(background)})`;
128
+ }
129
+ /**
130
+ * How each derived role computes when the host has not set it.
131
+ *
132
+ * **Never emitted at `:root`, and this is the one measured failure of the whole design
133
+ * rather than a caution:**
134
+ *
135
+ * ```css
136
+ * :root {
137
+ * --ui-color-hover: color-mix(in oklab, var(--ui-color-text) 8%, var(--ui-color-surface));
138
+ * }
139
+ * ```
140
+ *
141
+ * That resolves *once*, against the grounds in force at `:root`, and freezes. A dark
142
+ * subtree then inherits the light mix — a near-white hover on charcoal — with nothing to
143
+ * read anywhere. The formula belongs in the `var()` fallback at the point of use, where it
144
+ * resolves against the grounds in force *there*, which is what makes a derived role follow
145
+ * a theme and a scheme without being restated in either. `src/reference.ts` is what writes
146
+ * it, and `tests/tokens.test.ts` gates the rule rather than trusting this comment.
147
+ *
148
+ * Composed rather than written out, because a formula is data about *which* grounds a role
149
+ * mixes and in what proportion — and a hand-written string can name a token that does not
150
+ * exist, or forget the default above, and CSS reports either by rendering nothing.
151
+ *
152
+ * Exported because a target that is not CSS cannot evaluate `color-mix()` and has to
153
+ * resolve these itself, frozen per theme, from the same source the components read.
154
+ */
155
+ export const formulas = {
156
+ // A formula may be a plain reference. A purpose points into the scale; the scale is
157
+ // where the number lives, and a host who wants slower state changes moves the step.
158
+ '--ui-duration-state': ground('--ui-duration-100'),
159
+ // Mixing toward the text rather than toward black or white is what makes one formula
160
+ // right in both schemes: text is always the far pole from surface, so the mix darkens
161
+ // on a light page and lightens on a dark one, without either being named. The contrast
162
+ // against whatever sits on top rises either way rather than falling.
163
+ '--ui-color-hover': mix('--ui-color-text', 8, '--ui-color-surface'),
164
+ '--ui-color-pressed': mix('--ui-color-text', 14, '--ui-color-surface'),
165
+ '--ui-color-accent-hover': mix('--ui-color-text', 12, '--ui-color-accent'),
166
+ '--ui-color-accent-pressed': mix('--ui-color-text', 22, '--ui-color-accent'),
48
167
  };
49
168
  /**
50
169
  * The grounds whose value differs when the page is rendered dark.
@@ -77,9 +196,12 @@ export const darkScheme = {
77
196
  // text is the one thing in this set that must never be hard to read. Red-400 is 6.41.
78
197
  '--ui-color-danger': '#f87171',
79
198
  };
199
+ /** The category every duration name shares, which is what reduced motion collapses. */
200
+ const duration = '--ui-duration-';
80
201
  /**
81
202
  * The token defaults as a CSS rule, for a host that wants them without importing a
82
- * component. Returns the text of a `:root` block; a host inserts it however it prefers.
203
+ * component. Returns the text of a `:root` block and the reduced-motion rule beside it; a
204
+ * host inserts them however it prefers.
83
205
  *
84
206
  * **It declares `color-scheme` as well as the tokens, and that is deliberate.**
85
207
  * `color-scheme` is a real property rather than a custom one, so it can never be a token —
@@ -87,14 +209,35 @@ export const darkScheme = {
87
209
  * host would have to remember, and forgetting means dark mode simply never happens, with
88
210
  * no error anywhere to read. A host who wants something else — `only light`, say — governs
89
211
  * the order this sheet is inserted in, which is a knob they already hold.
212
+ *
213
+ * **Reduced motion is honoured here, once, rather than in each component.** A component
214
+ * reads `--ui-duration-state` and never learns why it changed, which is the argument for
215
+ * motion being tokens rather than literals: a hardcoded `150ms` is not merely
216
+ * un-overridable, it is an accessibility defect every component would have to fix on its
217
+ * own. The block declares the *derived* duration names as well as the ground ones — the
218
+ * only place either may appear at `:root`, and legal there precisely because what it
219
+ * declares is a literal rather than a formula. Without it, a host who tuned
220
+ * `--ui-duration-state` would keep their motion through the collapse, and the setting
221
+ * would be honoured for everyone except the people who had touched it.
90
222
  */
91
223
  export function tokenStyleSheet() {
92
- const body = tokens
224
+ const grounds = tokens
93
225
  .map((token) => {
94
226
  const dark = darkScheme[token];
95
227
  return ` ${token}: ${dark === undefined ? defaults[token] : `light-dark(${defaults[token]}, ${dark})`};`;
96
228
  })
97
229
  .join('\n');
98
- return `:root {\n color-scheme: light dark;\n${body}\n}`;
230
+ // Not zero, and the difference is not cosmetic: a zero-length transition fires no
231
+ // `transitionstart` and no `transitionend`, measured, so a component that awaits the
232
+ // end of one before removing itself waits forever — and only for the people who asked
233
+ // for less motion. At `0.01ms` the lifecycle still runs; the time is what goes.
234
+ const collapsed = [...tokens, ...derivedTokens]
235
+ .filter((token) => token.startsWith(duration))
236
+ .map((token) => ` ${token}: 0.01ms;`)
237
+ .join('\n');
238
+ return [
239
+ `:root {\n color-scheme: light dark;\n${grounds}\n}`,
240
+ `@media (prefers-reduced-motion: reduce) {\n :root {\n${collapsed}\n }\n}`,
241
+ ].join('\n\n');
99
242
  }
100
243
  //# sourceMappingURL=tokens.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,mEAAmE;AACnE,MAAM,CAAC,MAAM,MAAM,GAAG;IAClB,mBAAmB;IACnB,4BAA4B;IAC5B,oBAAoB;IACpB,iBAAiB;IACjB,kBAAkB;IAClB,mBAAmB;IACnB,aAAa;IACb,YAAY;IACZ,WAAW;CACL,CAAC;AAKX;;;;;GAKG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAoC;IACrD,mBAAmB,EAAE,SAAS;IAC9B,4BAA4B,EAAE,SAAS;IACvC,oBAAoB,EAAE,SAAS;IAC/B,iBAAiB,EAAE,SAAS;IAC5B,mFAAmF;IACnF,qFAAqF;IACrF,gFAAgF;IAChF,mFAAmF;IACnF,iFAAiF;IACjF,oFAAoF;IACpF,iFAAiF;IACjF,4BAA4B;IAC5B,kBAAkB,EAAE,SAAS;IAC7B,mBAAmB,EAAE,SAAS;IAC9B,aAAa,EAAE,UAAU;IACzB,YAAY,EAAE,QAAQ;IACtB,WAAW,EAAE,uBAAuB;CACvC,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,UAAU,GAA6C;IAChE,qFAAqF;IACrF,iFAAiF;IACjF,oCAAoC;IACpC,mBAAmB,EAAE,SAAS;IAC9B,4BAA4B,EAAE,SAAS;IACvC,oBAAoB,EAAE,SAAS;IAC/B,iBAAiB,EAAE,SAAS;IAC5B,oFAAoF;IACpF,sFAAsF;IACtF,mBAAmB,EAAE,SAAS;CACjC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe;IAC3B,MAAM,IAAI,GAAG,MAAM;SACd,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACX,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QAE/B,OAAO,KAAK,KAAK,KAAK,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,cAAc,QAAQ,CAAC,KAAK,CAAC,KAAK,IAAI,GAAG,GAAG,CAAC;IAC9G,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEhB,OAAO,yCAAyC,IAAI,KAAK,CAAC;AAC9D,CAAC"}
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rak200/ui",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Host-agnostic custom-element UI components for the rak200 ecosystem",
5
5
  "license": "MIT",
6
6
  "author": "rak200 <rak.ricardo@windowslive.com>",