@jonmilner/ui 0.5.4 → 0.6.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.
@@ -11,10 +11,10 @@ import { CircleCheckIcon as c } from "../icons/CircleCheckIcon.mjs";
11
11
  import { InfoIcon as l } from "../icons/InfoIcon.mjs";
12
12
  import { announcer as u, close as d, content as f, list as p, root as m, toast as h, viewport as g } from "./Toast.css.mjs";
13
13
  import { jsx as _, jsxs as v } from "react/jsx-runtime";
14
- import { createContext as y, use as b, useCallback as x, useLayoutEffect as S, useMemo as C, useRef as w, useState as T } from "react";
15
- import { Toast as E } from "@base-ui/react/toast";
14
+ import { createContext as y, use as b, useCallback as x, useEffect as S, useLayoutEffect as C, useMemo as w, useRef as T, useState as E } from "react";
15
+ import { Toast as D } from "@base-ui/react/toast";
16
16
  //#region src/Toast/client.tsx
17
- var D = {
17
+ var O = {
18
18
  info: {
19
19
  Icon: l,
20
20
  color: "fgMuted"
@@ -27,13 +27,13 @@ var D = {
27
27
  Icon: o,
28
28
  color: "danger"
29
29
  }
30
- }, O = y(null), k = y(null), A = (e) => e !== void 0 && Object.hasOwn(D, e);
31
- function j({ limit: e = 3, closeLabel: n = "Dismiss", regionLabel: r = "Notifications", className: i, style: o, children: s }) {
32
- let { container: c, ref: l } = a(), [u] = T(E.createToastManager);
33
- return /* @__PURE__ */ _(E.Provider, {
30
+ }, k = y(null), A = y(null), j = (e) => e !== void 0 && Object.hasOwn(O, e);
31
+ function M({ limit: e = 3, closeLabel: n = "Dismiss", regionLabel: r = "Notifications", className: i, style: o, children: s }) {
32
+ let { container: c, ref: l } = a(), [u] = E(D.createToastManager);
33
+ return /* @__PURE__ */ _(D.Provider, {
34
34
  limit: e,
35
35
  toastManager: u,
36
- children: /* @__PURE__ */ v(O, {
36
+ children: /* @__PURE__ */ v(k, {
37
37
  value: u,
38
38
  children: [
39
39
  /* @__PURE__ */ _(t, {
@@ -42,23 +42,38 @@ function j({ limit: e = 3, closeLabel: n = "Dismiss", regionLabel: r = "Notifica
42
42
  ref: l
43
43
  }),
44
44
  s,
45
- /* @__PURE__ */ _(N, {}),
46
- /* @__PURE__ */ _(E.Portal, {
45
+ /* @__PURE__ */ _(P, {}),
46
+ /* @__PURE__ */ _(D.Portal, {
47
47
  container: c,
48
- children: /* @__PURE__ */ _(M, {
48
+ children: /* @__PURE__ */ _(N, {
49
49
  className: i,
50
50
  regionLabel: r,
51
51
  style: o,
52
- children: /* @__PURE__ */ _(P, { closeLabel: n })
52
+ children: /* @__PURE__ */ _(F, { closeLabel: n })
53
53
  })
54
54
  })
55
55
  ]
56
56
  })
57
57
  });
58
58
  }
59
- function M({ regionLabel: n, className: r, style: i, children: a }) {
60
- let o = w(null);
61
- return /* @__PURE__ */ _(E.Viewport, {
59
+ function N({ regionLabel: n, className: r, style: i, children: a }) {
60
+ let o = T(null), s = T(null);
61
+ return S(() => {
62
+ let e = s.current;
63
+ if (!e) return;
64
+ let t = e.ownerDocument, n = !1, r = (t) => {
65
+ e.contains(t.target) ? n = !0 : n && (n = !1, e.dispatchEvent(new MouseEvent("mouseout", {
66
+ bubbles: !0,
67
+ relatedTarget: e.parentElement
68
+ })));
69
+ }, i = (t) => {
70
+ let r = t.relatedTarget;
71
+ (!r || !e.contains(r)) && (n = !1);
72
+ };
73
+ return t.addEventListener("mouseover", r), t.addEventListener("mouseout", i), () => {
74
+ t.removeEventListener("mouseover", r), t.removeEventListener("mouseout", i);
75
+ };
76
+ }, []), /* @__PURE__ */ _(D.Viewport, {
62
77
  "aria-atomic": void 0,
63
78
  "aria-label": n,
64
79
  "aria-live": "off",
@@ -67,6 +82,7 @@ function M({ regionLabel: n, className: r, style: i, children: a }) {
67
82
  let t = e.relatedTarget;
68
83
  t && !e.currentTarget.contains(t) && (o.current = t);
69
84
  },
85
+ ref: s,
70
86
  render: /* @__PURE__ */ _(t, {
71
87
  className: e(g, r),
72
88
  position: "fixed",
@@ -75,15 +91,15 @@ function M({ regionLabel: n, className: r, style: i, children: a }) {
75
91
  }),
76
92
  children: /* @__PURE__ */ _("div", {
77
93
  className: p,
78
- children: /* @__PURE__ */ _(k, {
94
+ children: /* @__PURE__ */ _(A, {
79
95
  value: o,
80
96
  children: a
81
97
  })
82
98
  })
83
99
  });
84
100
  }
85
- function N() {
86
- let { toasts: e } = E.useToastManager(), t = (t) => e.filter((e) => e.type === "error" === t).map((e) => /* @__PURE__ */ v("div", { children: [
101
+ function P() {
102
+ let { toasts: e } = D.useToastManager(), t = (t) => e.filter((e) => e.type === "error" === t).map((e) => /* @__PURE__ */ v("div", { children: [
87
103
  e.title,
88
104
  " ",
89
105
  e.description
@@ -103,18 +119,18 @@ function N() {
103
119
  })]
104
120
  });
105
121
  }
106
- function P({ closeLabel: e }) {
107
- let { toasts: t } = E.useToastManager();
108
- return t.map((t) => /* @__PURE__ */ _(F, {
122
+ function F({ closeLabel: e }) {
123
+ let { toasts: t } = D.useToastManager();
124
+ return t.map((t) => /* @__PURE__ */ _(I, {
109
125
  closeLabel: e,
110
126
  toast: t
111
127
  }, t.id));
112
128
  }
113
- function F({ toast: a, closeLabel: o }) {
114
- let c = A(a.type) ? a.type : "info", { Icon: l, color: u } = D[c], p = b(O), g = b(k), [y, C] = T(!1), j = x((e) => {
115
- e && C(getComputedStyle(e).direction === "rtl");
116
- }, []), M = w(!1), N = a.transitionStatus === "ending";
117
- return S(() => {
129
+ function I({ toast: a, closeLabel: o }) {
130
+ let c = j(a.type) ? a.type : "info", { Icon: l, color: u } = O[c], p = b(k), g = b(A), [y, S] = E(!1), w = x((e) => {
131
+ e && S(getComputedStyle(e).direction === "rtl");
132
+ }, []), M = T(!1), N = a.transitionStatus === "ending";
133
+ return C(() => {
118
134
  let e = a.ref?.current, t = e?.ownerDocument.activeElement;
119
135
  if (!N || M.current || !t || !e.contains(t)) return;
120
136
  let n = g?.current;
@@ -123,9 +139,9 @@ function F({ toast: a, closeLabel: o }) {
123
139
  N,
124
140
  g,
125
141
  a.ref
126
- ]), /* @__PURE__ */ v(E.Root, {
142
+ ]), /* @__PURE__ */ v(D.Root, {
127
143
  "aria-modal": void 0,
128
- ref: j,
144
+ ref: w,
129
145
  render: /* @__PURE__ */ _(t, {
130
146
  alignItems: "start",
131
147
  backgroundColor: "bgRaised",
@@ -154,27 +170,27 @@ function F({ toast: a, closeLabel: o }) {
154
170
  flexGrow: 1,
155
171
  gap: "8",
156
172
  children: [
157
- /* @__PURE__ */ _(E.Title, { render: /* @__PURE__ */ _(r, {
173
+ /* @__PURE__ */ _(D.Title, { render: /* @__PURE__ */ _(r, {
158
174
  as: "div",
159
175
  weight: "medium"
160
176
  }) }),
161
- /* @__PURE__ */ _(E.Description, { render: /* @__PURE__ */ _(r, {
177
+ /* @__PURE__ */ _(D.Description, { render: /* @__PURE__ */ _(r, {
162
178
  as: "div",
163
179
  color: "fgMuted",
164
180
  size: "sm"
165
181
  }) }),
166
- /* @__PURE__ */ _(E.Action, { render: /* @__PURE__ */ _(i, {
182
+ /* @__PURE__ */ _(D.Action, { render: /* @__PURE__ */ _(i, {
167
183
  size: "sm",
168
184
  variant: "secondary"
169
185
  }) })
170
186
  ]
171
187
  }),
172
- /* @__PURE__ */ _(E.Close, {
188
+ /* @__PURE__ */ _(D.Close, {
173
189
  "aria-hidden": !1,
174
190
  "aria-label": o,
175
191
  onClick: (e) => {
176
192
  if (e.detail !== 0 || !p) return;
177
- let t = a.ref?.current, n = (t && I(t)) ?? g?.current;
193
+ let t = a.ref?.current, n = (t && L(t)) ?? g?.current;
178
194
  n instanceof HTMLElement && (e.preventBaseUIHandler(), M.current = !0, p.close(a.id), requestAnimationFrame(() => {
179
195
  n.isConnected && n.focus();
180
196
  }));
@@ -190,14 +206,14 @@ function F({ toast: a, closeLabel: o }) {
190
206
  ]
191
207
  });
192
208
  }
193
- function I(e) {
209
+ function L(e) {
194
210
  let t = (e, n) => !e || !e.matches("[data-ending-style]") ? e : t(n ? e.nextElementSibling : e.previousElementSibling, n);
195
211
  return (t(e.nextElementSibling, !0) ?? t(e.previousElementSibling, !1))?.querySelector(`.${d}`);
196
212
  }
197
- function L() {
198
- let e = b(O);
213
+ function R() {
214
+ let e = b(k);
199
215
  if (!e) throw Error("useToast must be called under Toast.Provider.");
200
- return C(() => {
216
+ return w(() => {
201
217
  let t = (t) => e.close(t);
202
218
  return {
203
219
  add: ({ title: n, description: r, variant: i = "info", duration: a = i === "error" ? 0 : 5e3, action: o }) => {
@@ -225,4 +241,4 @@ function L() {
225
241
  }, [e]);
226
242
  }
227
243
  //#endregion
228
- export { N as ToastAnnouncer, F as ToastItem, j as ToastProvider, M as ToastStack, L as useToast };
244
+ export { P as ToastAnnouncer, I as ToastItem, M as ToastProvider, N as ToastStack, R as useToast };
package/dist/base.css CHANGED
@@ -1,6 +1,6 @@
1
1
  @layer tokens, reset, atoms, components.box, components.checkbox, components.clickable, components.icon, components.icon-button, components.inline, components.stack, components.text, components.avatar, components.badge, components.button, components.chip, components.field, components.color-field, components.heading, components.dialog, components.alert-dialog, components.menu, components.multiselect, components.search-field, components.tabs, components.tag, components.text-field, components.toast, components.visually-hidden;
2
2
  @layer tokens{:root{--space-0:0rem;--space-1:.0625rem;--space-2:.125rem;--space-4:.25rem;--space-6:.375rem;--space-8:.5rem;--space-12:.75rem;--space-16:1rem;--space-20:1.25rem;--space-24:1.5rem;--space-32:2rem;--space-40:2.5rem;--space-48:3rem;--space-64:4rem;--space-80:5rem;--space-96:6rem;--size-xs:1.5rem;--size-sm:2rem;--size-med:2.5rem;--size-lg:3rem;--size-xl:3.5rem;--container-prose:40rem;--container-page:64rem;--container-wide:80rem;--radius-none:0;--radius-sm:.25rem;--radius-med:.5rem;--radius-lg:.75rem;--radius-xl:1rem;--radius-full:9999px;--font-size-xs:.75rem;--font-size-sm:.875rem;--font-size-med:1rem;--font-size-lg:1.125rem;--font-size-xl:1.25rem;--font-size-2xl:1.5rem;--font-size-3xl:1.875rem;--font-size-4xl:2.25rem;--font-weight-regular:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--line-height-sm:1.25;--line-height-med:1.5;--line-height-lg:1.75;--z-index-base:0;--z-index-raised:1;--z-index-dropdown:1000;--z-index-sticky:1100;--z-index-overlay:1200;--z-index-modal:1300;--z-index-popover:1400;--z-index-toast:1500;--z-index-tooltip:1600;--duration-fast:.12s;--duration-med:.2s;--duration-slow:.32s;--easing-standard:cubic-bezier(.2, 0, 0, 1);--easing-enter:cubic-bezier(0, 0, 0, 1);--easing-exit:cubic-bezier(.3, 0, 1, 1);--distance-sm:.25rem;--distance-med:.5rem;--distance-lg:1rem;--scale-sm:.02;--scale-med:.05;--scale-lg:.1}[data-brand]{--shadow-sm:0 1px 2px color-mix(in srgb, var(--color-shadow) 12%, transparent);--shadow-med:0 2px 8px color-mix(in srgb, var(--color-shadow) 14%, transparent), 0 1px 2px color-mix(in srgb, var(--color-shadow) 8%, transparent);--shadow-lg:0 8px 24px color-mix(in srgb, var(--color-shadow) 16%, transparent), 0 2px 6px color-mix(in srgb, var(--color-shadow) 8%, transparent)}[data-brand][data-mode=light]{color-scheme:light}[data-brand][data-mode=dark]{--shadow-sm:0 0 0 1px color-mix(in srgb, var(--color-fg) 10%, transparent), 0 1px 2px color-mix(in srgb, var(--color-shadow) 40%, transparent);--shadow-med:0 0 0 1px color-mix(in srgb, var(--color-fg) 10%, transparent), 0 2px 8px color-mix(in srgb, var(--color-shadow) 48%, transparent), 0 1px 2px color-mix(in srgb, var(--color-shadow) 32%, transparent);--shadow-lg:0 0 0 1px color-mix(in srgb, var(--color-fg) 10%, transparent), 0 8px 24px color-mix(in srgb, var(--color-shadow) 56%, transparent), 0 2px 6px color-mix(in srgb, var(--color-shadow) 32%, transparent);color-scheme:dark}[data-brand][data-mode=system]{color-scheme:light dark}:dir(ltr){--dir:1}:dir(rtl){--dir:-1}@media (prefers-color-scheme:dark){[data-brand][data-mode=system]{--shadow-sm:0 0 0 1px color-mix(in srgb, var(--color-fg) 10%, transparent), 0 1px 2px color-mix(in srgb, var(--color-shadow) 40%, transparent);--shadow-med:0 0 0 1px color-mix(in srgb, var(--color-fg) 10%, transparent), 0 2px 8px color-mix(in srgb, var(--color-shadow) 48%, transparent), 0 1px 2px color-mix(in srgb, var(--color-shadow) 32%, transparent);--shadow-lg:0 0 0 1px color-mix(in srgb, var(--color-fg) 10%, transparent), 0 8px 24px color-mix(in srgb, var(--color-shadow) 56%, transparent), 0 2px 6px color-mix(in srgb, var(--color-shadow) 32%, transparent)}}@media (prefers-reduced-motion:reduce){:root{--distance-sm:0rem;--distance-med:0rem;--distance-lg:0rem;--scale-sm:0;--scale-med:0;--scale-lg:0;--duration-fast:.12s;--duration-med:.12s;--duration-slow:.12s}}}
3
3
 
4
- @font-face{src:local(Arial),local(ArialMT);font-weight:400;font-style:normal;size-adjust:101.568%;ascent-override:95.3892%;descent-override:23.7511%;line-gap-override:0%;font-family:Inter Fallback}@font-face{src:local(Arial Bold),local(Arial-BoldMT);font-weight:700;font-style:normal;size-adjust:101.637%;ascent-override:95.3241%;descent-override:23.7349%;line-gap-override:0%;font-family:Inter Fallback}@font-face{src:local(Arial Italic),local(Arial-ItalicMT);font-weight:400;font-style:italic;size-adjust:101.637%;ascent-override:95.3241%;descent-override:23.7349%;line-gap-override:0%;font-family:Inter Fallback}@font-face{src:local(Arial Bold Italic),local(Arial-BoldItalicMT);font-weight:700;font-style:italic;size-adjust:101.706%;ascent-override:95.2591%;descent-override:23.7187%;line-gap-override:0%;font-family:Inter Fallback}@font-face{src:local(Courier New),local(CourierNewPSMT);font-weight:400;font-style:normal;size-adjust:127.781%;ascent-override:79.8319%;descent-override:23.48%;line-gap-override:0%;font-family:JetBrains Mono Fallback}@font-face{src:local(Courier New Bold),local(CourierNewPS-BoldMT);font-weight:700;font-style:normal;size-adjust:123.353%;ascent-override:82.6977%;descent-override:24.3229%;line-gap-override:0%;font-family:JetBrains Mono Fallback}@font-face{src:local(Courier New Italic),local(CourierNewPS-ItalicMT);font-weight:400;font-style:italic;size-adjust:127.781%;ascent-override:79.8319%;descent-override:23.48%;line-gap-override:0%;font-family:JetBrains Mono Fallback}@font-face{src:local(Courier New Bold Italic),local(CourierNewPS-BoldItalicMT);font-weight:700;font-style:italic;size-adjust:123.353%;ascent-override:82.6977%;descent-override:24.3229%;line-gap-override:0%;font-family:JetBrains Mono Fallback}@layer tokens{[data-brand=default]{--color-bg:light-dark(oklch(100% 0 0),oklch(14.5% .006 265));--color-bg-muted:light-dark(oklch(96.8% .004 265),oklch(27% .01 265));--color-bg-raised:light-dark(oklch(100% 0 0),oklch(20.5% .008 265));--color-fg:light-dark(oklch(14.5% .006 265),oklch(98.5% .002 265));--color-fg-muted:light-dark(oklch(44.5% .014 265),oklch(71% .012 265));--color-border:light-dark(oklch(92.5% .006 265),oklch(27% .01 265));--color-border-strong:oklch(55.5% .014 265);--color-accent:light-dark(oklch(54.6% .19 258),oklch(70.7% .14 258));--color-accent-fg:light-dark(oklch(100% 0 0),oklch(14.5% .006 265));--color-danger:light-dark(oklch(56% .2 25),oklch(70.4% .17 25));--color-danger-fg:light-dark(oklch(100% 0 0),oklch(14.5% .006 265));--color-success:light-dark(oklch(52% .135 150),oklch(76% .15 150));--color-success-fg:light-dark(oklch(100% 0 0),oklch(14.5% .006 265));--color-warning:light-dark(oklch(53% .11 75),oklch(81% .145 75));--color-warning-fg:light-dark(oklch(100% 0 0),oklch(14.5% .006 265));--color-focus:light-dark(oklch(54.6% .19 258),oklch(70.7% .14 258));--color-shadow:light-dark(oklch(14.5% .006 265),oklch(0% 0 0));--font-body:"Inter", "Inter Fallback", sans-serif;--font-heading:"Inter", "Inter Fallback", sans-serif;--font-display:"Inter", "Inter Fallback", sans-serif;--font-label:"Inter", "Inter Fallback", sans-serif;--font-mono:"JetBrains Mono", "JetBrains Mono Fallback", monospace;--font-trim-body-cap:.36377;--font-trim-body-baseline:.36377;--font-trim-heading-cap:.36377;--font-trim-heading-baseline:.36377;--font-trim-display-cap:.36377;--font-trim-display-baseline:.36377;--font-trim-label-cap:.36377;--font-trim-label-baseline:.36377;--font-trim-mono-cap:.37;--font-trim-mono-baseline:.36}@media (forced-colors:active){[data-brand][data-brand]{--color-bg:Canvas;--color-bg-muted:Canvas;--color-bg-raised:Canvas;--color-fg:CanvasText;--color-fg-muted:CanvasText;--color-border:CanvasText;--color-border-strong:CanvasText;--color-accent:LinkText;--color-accent-fg:Canvas;--color-danger:CanvasText;--color-danger-fg:Canvas;--color-success:CanvasText;--color-success-fg:Canvas;--color-warning:CanvasText;--color-warning-fg:Canvas;--color-focus:Highlight;--color-shadow:CanvasText}}}
4
+ @font-face{src:local(Arial),local(ArialMT);font-weight:400;font-style:normal;size-adjust:101.568%;ascent-override:95.3892%;descent-override:23.7511%;line-gap-override:0%;font-family:Inter Fallback Arial}@font-face{src:local(Arial Bold),local(Arial-BoldMT);font-weight:700;font-style:normal;size-adjust:101.637%;ascent-override:95.3241%;descent-override:23.7349%;line-gap-override:0%;font-family:Inter Fallback Arial}@font-face{src:local(Arial Italic),local(Arial-ItalicMT);font-weight:400;font-style:italic;size-adjust:101.637%;ascent-override:95.3241%;descent-override:23.7349%;line-gap-override:0%;font-family:Inter Fallback Arial}@font-face{src:local(Arial Bold Italic),local(Arial-BoldItalicMT);font-weight:700;font-style:italic;size-adjust:101.706%;ascent-override:95.2591%;descent-override:23.7187%;line-gap-override:0%;font-family:Inter Fallback Arial}@font-face{src:local(Menlo Regular),local(Menlo-Regular);font-weight:400;font-style:normal;size-adjust:100.137%;ascent-override:101.871%;descent-override:29.9621%;line-gap-override:0%;font-family:JetBrains Mono Fallback Menlo}@font-face{src:local(Menlo Bold),local(Menlo-Bold);font-weight:700;font-style:normal;size-adjust:100.003%;ascent-override:102.007%;descent-override:30.0022%;line-gap-override:0%;font-family:JetBrains Mono Fallback Menlo}@font-face{src:local(Menlo Italic),local(Menlo-Italic);font-weight:400;font-style:italic;size-adjust:100.137%;ascent-override:101.871%;descent-override:29.9621%;line-gap-override:0%;font-family:JetBrains Mono Fallback Menlo}@font-face{src:local(Menlo Bold Italic),local(Menlo-BoldItalic);font-weight:700;font-style:italic;size-adjust:100.003%;ascent-override:102.007%;descent-override:30.0022%;line-gap-override:0%;font-family:JetBrains Mono Fallback Menlo}@font-face{src:local(Consolas);font-weight:400;font-style:normal;size-adjust:114.387%;ascent-override:89.1798%;descent-override:26.2293%;line-gap-override:0%;font-family:JetBrains Mono Fallback Consolas}@font-face{src:local(Consolas Bold),local(Consolas-Bold);font-weight:700;font-style:normal;size-adjust:114.387%;ascent-override:89.1798%;descent-override:26.2293%;line-gap-override:0%;font-family:JetBrains Mono Fallback Consolas}@font-face{src:local(Consolas Italic),local(Consolas-Italic);font-weight:400;font-style:italic;size-adjust:114.387%;ascent-override:89.1798%;descent-override:26.2293%;line-gap-override:0%;font-family:JetBrains Mono Fallback Consolas}@font-face{src:local(Consolas Bold Italic),local(Consolas-BoldItalic);font-weight:700;font-style:italic;size-adjust:114.387%;ascent-override:89.1798%;descent-override:26.2293%;line-gap-override:0%;font-family:JetBrains Mono Fallback Consolas}@font-face{src:local(Courier New),local(CourierNewPSMT);font-weight:400;font-style:normal;size-adjust:127.781%;ascent-override:79.8319%;descent-override:23.48%;line-gap-override:0%;font-family:JetBrains Mono Fallback Courier New}@font-face{src:local(Courier New Bold),local(CourierNewPS-BoldMT);font-weight:700;font-style:normal;size-adjust:123.353%;ascent-override:82.6977%;descent-override:24.3229%;line-gap-override:0%;font-family:JetBrains Mono Fallback Courier New}@font-face{src:local(Courier New Italic),local(CourierNewPS-ItalicMT);font-weight:400;font-style:italic;size-adjust:127.781%;ascent-override:79.8319%;descent-override:23.48%;line-gap-override:0%;font-family:JetBrains Mono Fallback Courier New}@font-face{src:local(Courier New Bold Italic),local(CourierNewPS-BoldItalicMT);font-weight:700;font-style:italic;size-adjust:123.353%;ascent-override:82.6977%;descent-override:24.3229%;line-gap-override:0%;font-family:JetBrains Mono Fallback Courier New}@layer tokens{[data-brand=default]{--color-bg:light-dark(oklch(100% 0 0),oklch(14.5% .006 265));--color-bg-muted:light-dark(oklch(96.8% .004 265),oklch(27% .01 265));--color-bg-raised:light-dark(oklch(100% 0 0),oklch(20.5% .008 265));--color-fg:light-dark(oklch(14.5% .006 265),oklch(98.5% .002 265));--color-fg-muted:light-dark(oklch(44.5% .014 265),oklch(71% .012 265));--color-border:light-dark(oklch(92.5% .006 265),oklch(27% .01 265));--color-border-strong:oklch(55.5% .014 265);--color-accent:light-dark(oklch(54.6% .19 258),oklch(70.7% .14 258));--color-accent-fg:light-dark(oklch(100% 0 0),oklch(14.5% .006 265));--color-danger:light-dark(oklch(56% .2 25),oklch(70.4% .17 25));--color-danger-fg:light-dark(oklch(100% 0 0),oklch(14.5% .006 265));--color-success:light-dark(oklch(52% .135 150),oklch(76% .15 150));--color-success-fg:light-dark(oklch(100% 0 0),oklch(14.5% .006 265));--color-warning:light-dark(oklch(53% .11 75),oklch(81% .145 75));--color-warning-fg:light-dark(oklch(100% 0 0),oklch(14.5% .006 265));--color-focus:light-dark(oklch(54.6% .19 258),oklch(70.7% .14 258));--color-shadow:light-dark(oklch(14.5% .006 265),oklch(0% 0 0));--font-body:"Inter", "Inter Fallback Arial", sans-serif;--font-heading:"Inter", "Inter Fallback Arial", sans-serif;--font-display:"Inter", "Inter Fallback Arial", sans-serif;--font-label:"Inter", "Inter Fallback Arial", sans-serif;--font-mono:"JetBrains Mono", "JetBrains Mono Fallback Menlo", "JetBrains Mono Fallback Consolas", "JetBrains Mono Fallback Courier New", monospace;--font-trim-body-cap:.36377;--font-trim-body-baseline:.36377;--font-trim-heading-cap:.36377;--font-trim-heading-baseline:.36377;--font-trim-display-cap:.36377;--font-trim-display-baseline:.36377;--font-trim-label-cap:.36377;--font-trim-label-baseline:.36377;--font-trim-mono-cap:.37;--font-trim-mono-baseline:.36}@media (forced-colors:active){[data-brand][data-brand]{--color-bg:Canvas;--color-bg-muted:Canvas;--color-bg-raised:Canvas;--color-fg:CanvasText;--color-fg-muted:CanvasText;--color-border:CanvasText;--color-border-strong:CanvasText;--color-accent:LinkText;--color-accent-fg:Canvas;--color-danger:CanvasText;--color-danger-fg:Canvas;--color-success:CanvasText;--color-success-fg:Canvas;--color-warning:CanvasText;--color-warning-fg:Canvas;--color-focus:Highlight;--color-shadow:CanvasText}}}
5
5
 
6
6
  @layer reset{*,:before,:after{box-sizing:border-box}*{margin:0}html{-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;min-block-size:100dvb;line-height:1.5}img,picture,video,canvas,svg{max-inline-size:100%;display:block}input,button,textarea,select{font:inherit;color:inherit}p,h1,h2,h3,h4,h5,h6{overflow-wrap:break-word}h1,h2,h3,h4,h5,h6{text-wrap:balance}}
@@ -1,7 +1,7 @@
1
1
  import { lightDark as e } from "./brand.mjs";
2
- import { arial as t, courierNew as n, inter as r, jetBrainsMono as i } from "./metrics.mjs";
2
+ import { arial as t, consolas as n, courierNew as r, inter as i, jetBrainsMono as a, menlo as o } from "./metrics.mjs";
3
3
  //#region src/brands/default.ts
4
- var a = (e, t) => Object.fromEntries(Object.entries(t).map(([t, [n, r]]) => [t, `oklch(${n} ${r} ${e})`])), o = "oklch(1 0 0)", s = "oklch(0 0 0)", c = a(265, {
4
+ var s = (e, t) => Object.fromEntries(Object.entries(t).map(([t, [n, r]]) => [t, `oklch(${n} ${r} ${e})`])), c = "oklch(1 0 0)", l = "oklch(0 0 0)", u = s(265, {
5
5
  50: [.985, .002],
6
6
  100: [.968, .004],
7
7
  200: [.925, .006],
@@ -13,7 +13,7 @@ var a = (e, t) => Object.fromEntries(Object.entries(t).map(([t, [n, r]]) => [t,
13
13
  800: [.27, .01],
14
14
  900: [.205, .008],
15
15
  950: [.145, .006]
16
- }), l = a(258, {
16
+ }), d = s(258, {
17
17
  50: [.97, .014],
18
18
  100: [.932, .032],
19
19
  200: [.882, .056],
@@ -25,7 +25,7 @@ var a = (e, t) => Object.fromEntries(Object.entries(t).map(([t, [n, r]]) => [t,
25
25
  800: [.424, .155],
26
26
  900: [.379, .135],
27
27
  950: [.282, .09]
28
- }), u = a(25, {
28
+ }), f = s(25, {
29
29
  50: [.971, .013],
30
30
  100: [.936, .032],
31
31
  200: [.885, .06],
@@ -37,7 +37,7 @@ var a = (e, t) => Object.fromEntries(Object.entries(t).map(([t, [n, r]]) => [t,
37
37
  800: [.444, .16],
38
38
  900: [.396, .13],
39
39
  950: [.258, .085]
40
- }), d = a(150, {
40
+ }), p = s(150, {
41
41
  50: [.975, .018],
42
42
  100: [.945, .04],
43
43
  200: [.9, .075],
@@ -49,7 +49,7 @@ var a = (e, t) => Object.fromEntries(Object.entries(t).map(([t, [n, r]]) => [t,
49
49
  800: [.41, .1],
50
50
  900: [.36, .085],
51
51
  950: [.26, .06]
52
- }), f = a(75, {
52
+ }), m = s(75, {
53
53
  50: [.98, .012],
54
54
  100: [.955, .03],
55
55
  200: [.915, .06],
@@ -61,42 +61,46 @@ var a = (e, t) => Object.fromEntries(Object.entries(t).map(([t, [n, r]]) => [t,
61
61
  800: [.42, .085],
62
62
  900: [.37, .07],
63
63
  950: [.27, .05]
64
- }), p = {
65
- metrics: r,
64
+ }), h = {
65
+ metrics: i,
66
66
  fallback: t,
67
67
  generic: "sans-serif"
68
- }, m = {
69
- metrics: i,
70
- fallback: n,
68
+ }, g = {
69
+ metrics: a,
70
+ fallback: [
71
+ o,
72
+ n,
73
+ r
74
+ ],
71
75
  generic: "monospace"
72
- }, h = {
76
+ }, _ = {
73
77
  name: "default",
74
78
  color: {
75
- bg: e(o, c[950]),
76
- bgMuted: e(c[100], c[800]),
77
- bgRaised: e(o, c[900]),
78
- fg: e(c[950], c[50]),
79
- fgMuted: e(c[600], c[400]),
80
- border: e(c[200], c[800]),
81
- borderStrong: e(c[500], c[500]),
82
- accent: e(l[600], l[400]),
83
- accentFg: e(o, c[950]),
84
- danger: e(u[600], u[400]),
85
- dangerFg: e(o, c[950]),
86
- success: e(d[600], d[400]),
87
- successFg: e(o, c[950]),
88
- warning: e(f[600], f[400]),
89
- warningFg: e(o, c[950]),
90
- focus: e(l[600], l[400]),
91
- shadow: e(c[950], s)
79
+ bg: e(c, u[950]),
80
+ bgMuted: e(u[100], u[800]),
81
+ bgRaised: e(c, u[900]),
82
+ fg: e(u[950], u[50]),
83
+ fgMuted: e(u[600], u[400]),
84
+ border: e(u[200], u[800]),
85
+ borderStrong: e(u[500], u[500]),
86
+ accent: e(d[600], d[400]),
87
+ accentFg: e(c, u[950]),
88
+ danger: e(f[600], f[400]),
89
+ dangerFg: e(c, u[950]),
90
+ success: e(p[600], p[400]),
91
+ successFg: e(c, u[950]),
92
+ warning: e(m[600], m[400]),
93
+ warningFg: e(c, u[950]),
94
+ focus: e(d[600], d[400]),
95
+ shadow: e(u[950], l)
92
96
  },
93
97
  font: {
94
- body: p,
95
- heading: p,
96
- display: p,
97
- label: p,
98
- mono: m
98
+ body: h,
99
+ heading: h,
100
+ display: h,
101
+ label: h,
102
+ mono: g
99
103
  }
100
104
  };
101
105
  //#endregion
102
- export { h as defaultBrand };
106
+ export { _ as defaultBrand };
@@ -13,7 +13,7 @@ export interface FontMetrics {
13
13
  export type FontVariant = Omit<FontMetrics, "variants">;
14
14
  export interface BrandFont {
15
15
  readonly metrics: FontMetrics;
16
- readonly fallback: FontMetrics;
16
+ readonly fallback: FontMetrics | readonly [FontMetrics, ...FontMetrics[]];
17
17
  readonly generic: "sans-serif" | "serif" | "monospace";
18
18
  }
19
19
  //#endregion
@@ -1,43 +1,52 @@
1
1
  //#region src/brands/font.ts
2
- var e = (e) => `"${e}"`, t = (e) => String(Number(e.toFixed(6))), n = (e) => `${t(e * 100)}%`, r = (e) => `${e.metrics.familyName} Fallback`, i = (t) => [
2
+ var e = (e) => `"${e}"`, t = (e) => String(Number(e.toFixed(6))), n = (e) => `${t(e * 100)}%`;
3
+ function r({ metrics: e, fallback: t }) {
4
+ let n = [t].flat();
5
+ if (!n.length) throw Error(`"${e.familyName}" lists no fallback font`);
6
+ return n.map((t) => ({
7
+ metrics: e,
8
+ fallback: t
9
+ }));
10
+ }
11
+ var i = ({ metrics: e, fallback: t }) => `${e.familyName} Fallback ${t.familyName}`, a = (t) => [
3
12
  e(t.metrics.familyName),
4
- e(r(t)),
13
+ ...r(t).map((t) => e(i(t))),
5
14
  t.generic
6
15
  ].join(", ");
7
- function a({ capHeight: e, ascent: n, descent: r, unitsPerEm: i }) {
16
+ function o({ capHeight: e, ascent: n, descent: r, unitsPerEm: i }) {
8
17
  let a = (n - Math.abs(r)) / i / 2;
9
18
  return {
10
19
  cap: t(e / i - a),
11
20
  baseline: t(a)
12
21
  };
13
22
  }
14
- var o = 1.0001, s = /^([1-9]00)?(italic)?$/, c = (e, t) => e.variants?.[t]?.familyName === e.familyName, l = ({ fallback: e }) => ["regular", ...Object.keys(e.variants ?? {}).filter((t) => t !== "" && s.test(t) && c(e, t))];
15
- function u({ metrics: t, fallback: r }, i = "regular") {
16
- let a = i === "regular", l = a ? r : c(r, i) && r.variants?.[i], u = s.exec(a ? "" : i);
17
- if (!l || !u) throw Error(`"${r.familyName}" has no ${i} variant`);
18
- let d = !a && c(t, i) && t.variants?.[i] || t, f = d.capHeight / d.unitsPerEm / (l.capHeight / l.unitsPerEm), p = d.unitsPerEm * f, m = [l.fullName ?? l.familyName, l.postscriptName];
23
+ var s = 1.0001, c = /^([1-9]00)?(italic)?$/, l = (e, t) => e.variants?.[t]?.familyName === e.familyName, u = ({ fallback: e }) => ["regular", ...Object.keys(e.variants ?? {}).filter((t) => t !== "" && c.test(t) && l(e, t))];
24
+ function d({ metrics: t, fallback: r }, i = "regular") {
25
+ let a = i === "regular", o = a ? r : l(r, i) && r.variants?.[i], u = c.exec(a ? "" : i);
26
+ if (!o || !u) throw Error(`"${r.familyName}" has no ${i} variant`);
27
+ let d = !a && l(t, i) && t.variants?.[i] || t, f = d.capHeight / d.unitsPerEm / (o.capHeight / o.unitsPerEm), p = d.unitsPerEm * f, m = [o.fullName ?? o.familyName, o.postscriptName];
19
28
  return {
20
29
  src: [...new Set(m)].filter(Boolean).map((t) => `local(${e(t)})`).join(", "),
21
30
  fontWeight: u[1] ?? "400",
22
31
  fontStyle: u[2] ? "italic" : "normal",
23
32
  sizeAdjust: n(f),
24
- ascentOverride: n(d.ascent / p * o),
25
- descentOverride: n(Math.abs(d.descent) / p * o),
33
+ ascentOverride: n(d.ascent / p * s),
34
+ descentOverride: n(Math.abs(d.descent) / p * s),
26
35
  lineGapOverride: n(d.lineGap / p)
27
36
  };
28
37
  }
29
- function d(e) {
38
+ function f(e) {
30
39
  let t = /* @__PURE__ */ new Map();
31
- for (let n of e) {
32
- let e = r(n), i = t.get(e) ?? /* @__PURE__ */ new Map();
33
- for (let t of l(n)) {
34
- let r = u(n, t), a = `${r.fontWeight} ${r.fontStyle}`, o = i.get(a);
35
- if (o && JSON.stringify(o) !== JSON.stringify(r)) throw Error(`"${e}" is declared with different metrics or fallbacks`);
36
- i.set(a, r);
40
+ for (let n of [...e].flatMap(r)) {
41
+ let e = i(n), r = t.get(e) ?? /* @__PURE__ */ new Map();
42
+ for (let t of u(n)) {
43
+ let i = d(n, t), a = `${i.fontWeight} ${i.fontStyle}`, o = r.get(a);
44
+ if (o && JSON.stringify(o) !== JSON.stringify(i)) throw Error(`"${e}" is declared with different metrics or fallbacks`);
45
+ r.set(a, i);
37
46
  }
38
- t.set(e, i);
47
+ t.set(e, r);
39
48
  }
40
49
  return new Map([...t].map(([e, t]) => [e, [...t.values()]]));
41
50
  }
42
51
  //#endregion
43
- export { u as fallbackFace, d as fallbackFaces, r as fallbackFamily, l as fallbackVariants, i as fontStack, a as fontTrim };
52
+ export { d as fallbackFace, f as fallbackFaces, i as fallbackFamily, r as fallbackPairs, u as fallbackVariants, a as fontStack, o as fontTrim };
@@ -99,6 +99,65 @@ var e = {
99
99
  unitsPerEm: 2048
100
100
  }
101
101
  }
102
+ }, i = {
103
+ familyName: "Menlo",
104
+ fullName: "Menlo Regular",
105
+ postscriptName: "Menlo-Regular",
106
+ capHeight: 1493,
107
+ ascent: 1901,
108
+ descent: -483,
109
+ lineGap: 0,
110
+ unitsPerEm: 2048
111
+ }, a = {
112
+ ...i,
113
+ capHeight: 1495
114
+ }, o = {
115
+ ...i,
116
+ variants: {
117
+ 700: {
118
+ ...a,
119
+ fullName: "Menlo Bold",
120
+ postscriptName: "Menlo-Bold"
121
+ },
122
+ italic: {
123
+ ...i,
124
+ fullName: "Menlo Italic",
125
+ postscriptName: "Menlo-Italic"
126
+ },
127
+ "700italic": {
128
+ ...a,
129
+ fullName: "Menlo Bold Italic",
130
+ postscriptName: "Menlo-BoldItalic"
131
+ }
132
+ }
133
+ }, s = {
134
+ familyName: "Consolas",
135
+ fullName: "Consolas",
136
+ postscriptName: "Consolas",
137
+ capHeight: 1307,
138
+ ascent: 1521,
139
+ descent: -527,
140
+ lineGap: 350,
141
+ unitsPerEm: 2048
142
+ }, c = {
143
+ ...s,
144
+ variants: {
145
+ 700: {
146
+ ...s,
147
+ fullName: "Consolas Bold",
148
+ postscriptName: "Consolas-Bold"
149
+ },
150
+ italic: {
151
+ ...s,
152
+ fullName: "Consolas Italic",
153
+ postscriptName: "Consolas-Italic"
154
+ },
155
+ "700italic": {
156
+ ...s,
157
+ fullName: "Consolas Bold Italic",
158
+ postscriptName: "Consolas-BoldItalic"
159
+ }
160
+ }
102
161
  };
103
162
  //#endregion
104
- export { n as arial, r as courierNew, e as inter, t as jetBrainsMono };
163
+ export { n as arial, c as consolas, r as courierNew, e as inter, t as jetBrainsMono, o as menlo };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "./manifest.schema.json",
3
3
  "package": "@jonmilner/ui",
4
- "version": "0.5.4",
4
+ "version": "0.6.0",
5
5
  "components": [
6
6
  {
7
7
  "name": "AlertDialog",
@@ -0,0 +1,38 @@
1
+ # 0.6.0
2
+
3
+ Each fallback face family is now named for the web font and the local font it pairs, `"<Family> Fallback <Local>"`, and `BrandFont.fallback` may be an ordered list, because `mono` now falls back to Menlo, Consolas, then Courier New, each sized on its own (decision 0118); `defaultBrand.font.mono.fallback` is that list.
4
+
5
+ ## Find
6
+
7
+ In the app's styles and source (`*.css`, `*.css.ts`, `*.ts`, `*.tsx`): the old family names, regex `Fallback(["';,)]|\s*$)` (`"Inter Fallback"`, `"JetBrains Mono Fallback"`, unquoted `Inter Fallback;`, or a custom brand's `"<Family> Fallback"`).
8
+
9
+ In `*.ts` and `*.tsx`: code that reads a brand font's fallback as one font, such as regex `\.fallback\.(familyName|fullName|postscriptName|capHeight|ascent|descent|lineGap|unitsPerEm|variants)\b`.
10
+
11
+ ## Replace
12
+
13
+ Name the pair's family. A preset's `sans` stays one family; `mono` is three, in stack order. Prefer `var(--font-mono)` (or `vars.font.mono`) to a hand-written stack, which keeps the order and any later change.
14
+
15
+ ```css
16
+ /* before */
17
+ font-family: "JetBrains Mono", "JetBrains Mono Fallback", monospace;
18
+ /* after */
19
+ font-family: var(--font-mono);
20
+
21
+ /* before */
22
+ font-family: "Inter Fallback";
23
+ /* after */
24
+ font-family: "Inter Fallback Arial";
25
+ ```
26
+
27
+ Code reading a fallback's metrics takes the first font of the list, or each in turn:
28
+
29
+ ```ts
30
+ // before
31
+ const name = brand.font.mono.fallback.familyName;
32
+ // after
33
+ const name = [brand.font.mono.fallback].flat()[0]!.familyName;
34
+ ```
35
+
36
+ ## Verify
37
+
38
+ Run `rg "Fallback([\"';,)]|\s*$)" --glob '!node_modules'` again: no match, since every family now ends in its local font (`"Inter Fallback Arial"`). Then the app's typecheck passes, which also catches a fallback read the regex missed (a destructured `fallback`), and with the web fonts blocked in devtools, code text renders in Menlo (macOS) or Consolas (Windows), not Courier New.
@@ -39,7 +39,7 @@ const inbox = defineTheme({
39
39
  - **Give a custom brand its own name.** A theme applies page-wide, not only inside its provider: a theme named `default` recolors every `default` provider on the page. The name must be a CSS identifier.
40
40
  - **Contrast is checked in development.** Outside production, `defineTheme` throws when a text, fill or focus pair fails WCAG AA in either mode, and lists the pairs. It also checks text on component fills mixed from your roles, such as the dark-mode menu highlight (`fg` 10% into `bgRaised`), where `fg`, `fgMuted` and `danger` need 4.5:1 too, and Button's hover (`fg` 15% over each variant's fill), where `fg`, `accentFg` and `dangerFg` need 4.5:1 on `bg`, `accent` and `danger` tinted. The roles a fill mixes must be opaque. Colors must be literal: hex, `rgb()`, `oklab()`, `oklch()`, `color-mix()` or `light-dark()`. Named colors other than `white`, `black` and `transparent` are rejected, as is a translucent background. `var()` may only reference another color role (`var(--color-accent)`).
41
41
  - **Override theme tokens in unlayered CSS or in the theme itself.** The theme is unlayered, so a token set in `@layer app` loses to it. Preset tokens live in `@layer tokens` and can be overridden from `@layer app`.
42
- - **Fallback faces are global.** A brand font's fallback face is named `"<Family> Fallback"`. The theme declares only faces `base.css` lacks. A font reusing a family name must agree with every face it shares, weight and style included: a fallback listing fewer variants (such as capsize's per-font `arial` import) reuses the others' faces, but one that sizes a shared face differently, or adds a weight or style to a preset's family, throws.
42
+ - **Fallback faces are global.** A brand font's fallback faces are named for the web font and the local font, `"<Family> Fallback <Local>"` (`"Inter Fallback Arial"`). The theme declares only faces `base.css` lacks. A font reusing a family name must agree with every face it shares, weight and style included: a fallback listing fewer variants (such as capsize's per-font `arial` import) reuses the others' faces, but one that sizes a shared face differently, or adds a weight or style to a preset's family, throws.
43
43
  - **Values must be plain.** Color values and font names may not contain CSS syntax (`;`, braces, `<`, `>`, `\`, `!`, `/*`) or, for font names, quotes; `defineTheme` throws in every mode.
44
44
  - **Each change gets its own `href`.** The `href` hashes the CSS, so a theme rebuilt with new colors replaces the old one instead of being deduped away.
45
45
  - Under forced colors, every role maps to a system color, as for presets.
@@ -58,4 +58,4 @@ A provider's `color-scheme` stops at its `div`, so the page canvas and scrollbar
58
58
 
59
59
  A brand declares its fonts; the app loads them (fontsource or self-hosted `@font-face`) and owns preloading. The `default` brand uses Inter and JetBrains Mono. In development, `ThemeProvider` warns in the console when a brand font has no `@font-face` or fails to load; in production it renders no check and the check's body is dead code.
60
60
 
61
- Until a web font loads, text renders in a fallback face sized to the web font's cap height, so the trim holds where the fallback font (Arial, Courier New) is installed; elsewhere the generic family renders unadjusted. Bold, italic and bold italic text get their own fallback faces (`local("Arial Bold")` and so on), each sized to the web font's matching weight and style; in a custom brand, give `fallback` (and `metrics`, for the web font's own sizes) a `variants` map keyed `700`, `italic` and `700italic`, as `@capsizecss/metrics`' `entireMetricsCollection` has them. A fallback without `variants` gets only the regular face, and the browser synthesizes bold and italic from it. Widths still differ: in the `default` brand, `mono` text is about 28% wider than JetBrains Mono, so expect code to overflow or wrap until the font loads. Where width matters, preload the mono font and load it with `font-display: block` (or `optional`, which may keep the fallback for that page view); a custom brand's gap depends on its own fallback.
61
+ Until a web font loads, text renders in a fallback face sized to the web font's cap height, so the trim holds where a fallback font is installed (Arial for `sans`; Menlo, Consolas or Courier New for `mono`, tried in that order); elsewhere the generic family renders unadjusted. In a custom brand, `fallback` takes one font or an ordered list, and each gets its own faces. Bold, italic and bold italic text get their own fallback faces (`local("Arial Bold")` and so on), each sized to the web font's matching weight and style; in a custom brand, give `fallback` (and `metrics`, for the web font's own sizes) a `variants` map keyed `700`, `italic` and `700italic`, as `@capsizecss/metrics`' `entireMetricsCollection` has them. A fallback without `variants` gets only the regular face, and the browser synthesizes bold and italic from it. Widths still differ: in the `default` brand, `mono` text is about 0.5% wider than JetBrains Mono in Menlo (macOS), 4.8% in Consolas (Windows) and 28% in Courier New (elsewhere), so expect code to overflow or wrap until the font loads where Courier New stands in. Where width matters, preload the mono font and load it with `font-display: block` (or `optional`, which may keep the fallback for that page view); a custom brand's gap depends on its own fallback.
@@ -1,88 +1,84 @@
1
- import { fallbackFaces as e, fallbackFamily as t, fontStack as n, fontTrim as r } from "../brands/font.mjs";
2
- import { forcedColors as i } from "../brands/forced-colors.mjs";
3
- import { presets as a } from "../brands/index.mjs";
4
- import { resolveColor as o } from "../contrast/color.mjs";
5
- import { checkContrast as s, contrastPairs as c } from "../contrast/contrast.mjs";
6
- import { checkMixes as l, contrastMixes as u } from "../contrast/mixes.mjs";
7
- import { colorRoles as d, fontRoles as f } from "../tokens/roles.mjs";
8
- import { vars as p } from "../tokens/contract.mjs";
1
+ import { fallbackFaces as e, fallbackFamily as t, fallbackPairs as n, fontStack as r, fontTrim as i } from "../brands/font.mjs";
2
+ import { forcedColors as a } from "../brands/forced-colors.mjs";
3
+ import { presets as o } from "../brands/index.mjs";
4
+ import { resolveColor as s } from "../contrast/color.mjs";
5
+ import { checkContrast as c, contrastPairs as l } from "../contrast/contrast.mjs";
6
+ import { checkMixes as u, contrastMixes as d } from "../contrast/mixes.mjs";
7
+ import { colorRoles as f, fontRoles as p } from "../tokens/roles.mjs";
8
+ import { vars as m } from "../tokens/contract.mjs";
9
9
  //#region src/theme/define-theme.ts
10
- var m = (e) => e.slice(4, -1), h = (e) => e.replace(/[A-Z]/g, (e) => `-${e.toLowerCase()}`), g = (e, t) => `${e} {\n${t.map(([e, t]) => ` ${e}: ${t};\n`).join("")}}`;
11
- function _(o) {
12
- let s = (e) => {
13
- throw Error(`defineTheme("${o.name}"): ${e}`);
10
+ var h = (e) => e.slice(4, -1), g = (e) => e.replace(/[A-Z]/g, (e) => `-${e.toLowerCase()}`), _ = (e, t) => `${e} {\n${t.map(([e, t]) => ` ${e}: ${t};\n`).join("")}}`;
11
+ function v(s) {
12
+ let c = (e) => {
13
+ throw Error(`defineTheme("${s.name}"): ${e}`);
14
14
  };
15
- /^[a-z][\w-]*$/i.test(o.name) || s("the name must be a CSS identifier");
16
- for (let [e, t] of Object.entries(o.color)) v.test(t) && s(`color.${e} must be a single color value`);
17
- for (let [e, { metrics: t, fallback: n }] of Object.entries(o.font)) for (let r of [
18
- t,
19
- n,
20
- ...Object.values(n.variants ?? {})
21
- ].flatMap((e) => [
15
+ /^[a-z][\w-]*$/i.test(s.name) || c("the name must be a CSS identifier");
16
+ for (let [e, t] of Object.entries(s.color)) y.test(t) && c(`color.${e} must be a single color value`);
17
+ for (let [e, t] of Object.entries(s.font)) for (let r of [t.metrics, ...n(t).flatMap(({ fallback: e }) => [e, ...Object.values(e.variants ?? {})])].flatMap((e) => [
22
18
  e.familyName,
23
19
  e.fullName,
24
20
  e.postscriptName
25
- ])) r !== void 0 && (v.test(r) || /["'\n]/.test(r)) && s(`font.${e} has a font name with quotes or CSS syntax: ${r}`);
26
- process.env.NODE_ENV !== "production" && C(o, s);
27
- let c = `[data-brand="${o.name}"]`, l = Object.values(o.font), u = Object.values(a).flatMap((e) => Object.values(e.font)), _;
21
+ ])) r !== void 0 && (y.test(r) || /["'\n]/.test(r)) && c(`font.${e} has a font name with quotes or CSS syntax: ${r}`);
22
+ process.env.NODE_ENV !== "production" && w(s, c);
23
+ let l = `[data-brand="${s.name}"]`, u = Object.values(s.font), d = Object.values(o).flatMap((e) => Object.values(e.font)), v;
28
24
  try {
29
- _ = e([...u, ...l]);
25
+ v = e([...d, ...u]);
30
26
  } catch (e) {
31
- return s(e.message);
27
+ return c(e.message);
32
28
  }
33
- let b = new Set(l.map(t));
34
- for (let [t, n] of e(u)) b.delete(t), _.get(t).length > n.length && s(`"${t}" is declared with a weight or style base.css lacks`);
35
- let x = [
36
- ...[..._].filter(([e]) => b.has(e)).flatMap(([e, t]) => t.map((t) => g("@font-face", [["font-family", `"${e}"`], ...Object.entries(t).map(([e, t]) => [h(e), t])]))),
37
- g(c, [...d.map((e) => [m(p.color[e]), o.color[e]]), ...f.flatMap((e) => {
38
- let t = r(o.font[e].metrics);
29
+ let x = new Set(u.flatMap(n).map(t));
30
+ for (let [t, n] of e(d)) x.delete(t), v.get(t).length > n.length && c(`"${t}" is declared with a weight or style base.css lacks`);
31
+ let S = [
32
+ ...[...v].filter(([e]) => x.has(e)).flatMap(([e, t]) => t.map((t) => _("@font-face", [["font-family", `"${e}"`], ...Object.entries(t).map(([e, t]) => [g(e), t])]))),
33
+ _(l, [...f.map((e) => [h(m.color[e]), s.color[e]]), ...p.flatMap((e) => {
34
+ let t = i(s.font[e].metrics);
39
35
  return [
40
- [m(p.font[e]), n(o.font[e])],
41
- [m(p.fontTrim[e].cap), t.cap],
42
- [m(p.fontTrim[e].baseline), t.baseline]
36
+ [h(m.font[e]), r(s.font[e])],
37
+ [h(m.fontTrim[e].cap), t.cap],
38
+ [h(m.fontTrim[e].baseline), t.baseline]
43
39
  ];
44
40
  })]),
45
- `@media (forced-colors: active) {\n${g(c, d.map((e) => [m(p.color[e]), i[e]])).split("\n").map((e) => ` ${e}`).join("\n")}\n}`
41
+ `@media (forced-colors: active) {\n${_(l, f.map((e) => [h(m.color[e]), a[e]])).split("\n").map((e) => ` ${e}`).join("\n")}\n}`
46
42
  ].join("\n");
47
43
  return {
48
- brand: o,
49
- href: `ui-theme-${o.name}-${y(x)}`,
50
- css: x
44
+ brand: s,
45
+ href: `ui-theme-${s.name}-${b(S)}`,
46
+ css: S
51
47
  };
52
48
  }
53
- var v = /[;{}<>\\!]|\/\*/;
54
- function y(e) {
49
+ var y = /[;{}<>\\!]|\/\*/;
50
+ function b(e) {
55
51
  let t = 2166136261;
56
52
  for (let n = 0; n < e.length; n++) t = Math.imul(t ^ e.charCodeAt(n), 16777619);
57
53
  return (t >>> 0).toString(36);
58
54
  }
59
- var b = /var\(\s*--color-([a-z-]+)\s*\)/g, x = new Map(d.map((e) => [h(e), e])), S = new Set(c.map((e) => e.bg));
60
- function C(e, t) {
61
- let n = (r, i = []) => (i.includes(r) && t(`color.${r} references itself through var()`), e.color[r].replace(b, (e, t) => {
62
- let a = x.get(t);
55
+ var x = /var\(\s*--color-([a-z-]+)\s*\)/g, S = new Map(f.map((e) => [g(e), e])), C = new Set(l.map((e) => e.bg));
56
+ function w(e, t) {
57
+ let n = (r, i = []) => (i.includes(r) && t(`color.${r} references itself through var()`), e.color[r].replace(x, (e, t) => {
58
+ let a = S.get(t);
63
59
  return a ? n(a, [...i, r]) : e;
64
- })), r = Object.fromEntries(d.map((e) => [e, n(e)]));
65
- for (let n of d) {
60
+ })), r = Object.fromEntries(f.map((e) => [e, n(e)]));
61
+ for (let n of f) {
66
62
  r[n].includes("var(") && t(`color.${n} is ${e.color[n]}: the contrast check needs literal colors, so use var() only to reference another color role`);
67
63
  for (let i of ["light", "dark"]) {
68
64
  let a;
69
65
  try {
70
- ({alpha: a} = o(r[n], i));
66
+ ({alpha: a} = s(r[n], i));
71
67
  } catch (r) {
72
68
  return t(`color.${n} is ${e.color[n]}, which the contrast check cannot resolve (${r.message}). Use hex, rgb(), oklab(), oklch(), color-mix() or light-dark(); named colors other than white, black and transparent are not supported`);
73
69
  }
74
- a < 1 && S.has(n) && t(`color.${n} is translucent in ${i} mode, but it is a background in a contrast pair and must be opaque`);
70
+ a < 1 && C.has(n) && t(`color.${n} is translucent in ${i} mode, but it is a background in a contrast pair and must be opaque`);
75
71
  }
76
72
  }
77
- for (let { name: e, fill: n } of u) for (let i of ["light", "dark"]) o(n(r), i).alpha < 1 && t(`the ${e} is translucent in ${i} mode: the roles it mixes must be opaque`);
73
+ for (let { name: e, fill: n } of d) for (let i of ["light", "dark"]) s(n(r), i).alpha < 1 && t(`the ${e} is translucent in ${i} mode: the roles it mixes must be opaque`);
78
74
  let i = {
79
75
  ...e,
80
76
  color: r
81
- }, a = s(i), c = [...a, ...l(i).filter((e) => !a.some((t) => t.fg === e.fg && t.mode === e.mode && t.ratio === e.ratio)).map(({ mix: e, ...t }) => ({
77
+ }, a = c(i), o = [...a, ...u(i).filter((e) => !a.some((t) => t.fg === e.fg && t.mode === e.mode && t.ratio === e.ratio)).map(({ mix: e, ...t }) => ({
82
78
  ...t,
83
79
  bg: `the ${e}`
84
80
  }))].toSorted((e, t) => e.mode === t.mode ? 0 : e.mode === "light" ? -1 : 1);
85
- c.length > 0 && t(`fails WCAG AA contrast:\n${c.map(({ fg: e, bg: t, mode: n, ratio: r, min: i }) => ` ${e} on ${t} (${n}): ${r.toFixed(2)}, needs ${i}`).join("\n")}`);
81
+ o.length > 0 && t(`fails WCAG AA contrast:\n${o.map(({ fg: e, bg: t, mode: n, ratio: r, min: i }) => ` ${e} on ${t} (${n}): ${r.toFixed(2)}, needs ${i}`).join("\n")}`);
86
82
  }
87
83
  //#endregion
88
- export { _ as defineTheme };
84
+ export { v as defineTheme };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jonmilner/ui",
3
- "version": "0.5.4",
3
+ "version": "0.6.0",
4
4
  "files": [
5
5
  "dist",
6
6
  "THIRD_PARTY_NOTICES.md"