@cobdfamily/clf-core 7.0.1

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.
Files changed (44) hide show
  1. package/README.md +250 -0
  2. package/dist/_variables.scss +973 -0
  3. package/dist/components/cobd-embed.d.ts +4 -0
  4. package/dist/components/cobd-embed.js +163 -0
  5. package/dist/components/cobd-nav.d.ts +1 -0
  6. package/dist/components/cobd-nav.js +383 -0
  7. package/dist/components/cobd-support.d.ts +26 -0
  8. package/dist/components/cobd-support.js +296 -0
  9. package/dist/components/font-scale-toggle.d.ts +9 -0
  10. package/dist/components/font-scale-toggle.js +159 -0
  11. package/dist/components/forms/checkbox.d.ts +1 -0
  12. package/dist/components/forms/checkbox.js +118 -0
  13. package/dist/components/forms/common.d.ts +17 -0
  14. package/dist/components/forms/common.js +137 -0
  15. package/dist/components/forms/index.d.ts +5 -0
  16. package/dist/components/forms/index.js +13 -0
  17. package/dist/components/forms/select.d.ts +1 -0
  18. package/dist/components/forms/select.js +132 -0
  19. package/dist/components/forms/submit.d.ts +1 -0
  20. package/dist/components/forms/submit.js +78 -0
  21. package/dist/components/forms/textarea.d.ts +1 -0
  22. package/dist/components/forms/textarea.js +95 -0
  23. package/dist/components/forms/textfield.d.ts +1 -0
  24. package/dist/components/forms/textfield.js +125 -0
  25. package/dist/components/index.d.ts +7 -0
  26. package/dist/components/index.js +33 -0
  27. package/dist/components/theme-toggle.d.ts +10 -0
  28. package/dist/components/theme-toggle.js +130 -0
  29. package/dist/i18n/chrome.json +94 -0
  30. package/dist/navs/cobd.ca.json +83 -0
  31. package/dist/navs/more-cobd.json +60 -0
  32. package/dist/navs.d.ts +27 -0
  33. package/dist/navs.js +193 -0
  34. package/dist/theming/font-scale-paint.d.ts +0 -0
  35. package/dist/theming/font-scale-paint.js +32 -0
  36. package/dist/theming/init.d.ts +1 -0
  37. package/dist/theming/init.js +19 -0
  38. package/dist/theming/runtime.d.ts +22 -0
  39. package/dist/theming/runtime.js +333 -0
  40. package/dist/tokens.css +972 -0
  41. package/dist/tokens.json +97 -0
  42. package/dist/tokens.scss +95 -0
  43. package/package.json +123 -0
  44. package/src/navs/schema.json +64 -0
@@ -0,0 +1,333 @@
1
+ // CLF theming runtime. Framework-agnostic ESM.
2
+ //
3
+ // Writes <html data-theme="..."> to match the CSS that
4
+ // build-tokens.mjs emits. Persists user choice via either
5
+ // localStorage (default) or a cookie scoped to a parent
6
+ // domain (so subdomains share a single preference).
7
+ const ATTR = "data-theme";
8
+ const DEFAULT_STORAGE_KEY = "cobd-theme";
9
+ const ALL_THEMES = [
10
+ "light", "dark", "hc-light", "hc-dark", "auto",
11
+ ];
12
+ const listeners = new Set();
13
+ let mediaCleanup = null;
14
+ function isBrowser() {
15
+ return typeof document !== "undefined"
16
+ && typeof window !== "undefined";
17
+ }
18
+ function isPref(v) {
19
+ return typeof v === "string"
20
+ && ALL_THEMES.includes(v);
21
+ }
22
+ // --- localStorage backend (default) -----------------------
23
+ function localStorageBackend(key) {
24
+ function safe() {
25
+ try {
26
+ if (typeof localStorage === "undefined")
27
+ return null;
28
+ localStorage.setItem("__cobd_probe__", "1");
29
+ localStorage.removeItem("__cobd_probe__");
30
+ return localStorage;
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ return {
37
+ read() {
38
+ const v = safe()?.getItem(key);
39
+ return isPref(v) ? v : null;
40
+ },
41
+ write(p) {
42
+ const s = safe();
43
+ if (!s)
44
+ return;
45
+ if (p === "auto")
46
+ s.removeItem(key);
47
+ else
48
+ s.setItem(key, p);
49
+ },
50
+ clear() { safe()?.removeItem(key); },
51
+ };
52
+ }
53
+ // --- cookie backend (cross-subdomain) ---------------------
54
+ function cookieBackend(name, domain, maxAgeDays) {
55
+ function read() {
56
+ if (!isBrowser())
57
+ return null;
58
+ const prefix = name + "=";
59
+ for (const part of document.cookie.split(";")) {
60
+ const p = part.trim();
61
+ if (p.startsWith(prefix)) {
62
+ const v = decodeURIComponent(p.slice(prefix.length));
63
+ return isPref(v) ? v : null;
64
+ }
65
+ }
66
+ return null;
67
+ }
68
+ function set(value) {
69
+ if (!isBrowser())
70
+ return;
71
+ const parts = [
72
+ `${name}=${value === null ? "" : encodeURIComponent(value)}`,
73
+ "Path=/",
74
+ "SameSite=Lax",
75
+ ];
76
+ if (location.protocol === "https:")
77
+ parts.push("Secure");
78
+ if (domain)
79
+ parts.push(`Domain=${domain}`);
80
+ if (value === null) {
81
+ parts.push("Max-Age=0");
82
+ }
83
+ else {
84
+ parts.push(`Max-Age=${maxAgeDays * 24 * 60 * 60}`);
85
+ }
86
+ document.cookie = parts.join("; ");
87
+ }
88
+ return {
89
+ read,
90
+ write(p) { p === "auto" ? set(null) : set(p); },
91
+ clear() { set(null); },
92
+ };
93
+ }
94
+ // --- Active backend (mutable so initTheming can swap it) ---
95
+ let backend = localStorageBackend(DEFAULT_STORAGE_KEY);
96
+ function readPref() {
97
+ return backend.read() ?? "auto";
98
+ }
99
+ function writePref(p) {
100
+ backend.write(p);
101
+ }
102
+ function osPrefersDark() {
103
+ if (!isBrowser())
104
+ return false;
105
+ return window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false;
106
+ }
107
+ function osPrefersHighContrast() {
108
+ if (!isBrowser())
109
+ return false;
110
+ return window.matchMedia?.("(prefers-contrast: more)").matches ?? false;
111
+ }
112
+ function resolve(pref) {
113
+ if (pref === "auto") {
114
+ const dark = osPrefersDark();
115
+ const hc = osPrefersHighContrast();
116
+ if (hc && dark)
117
+ return "hc-dark";
118
+ if (hc)
119
+ return "hc-light";
120
+ if (dark)
121
+ return "dark";
122
+ return "light";
123
+ }
124
+ return pref;
125
+ }
126
+ function apply(pref) {
127
+ if (!isBrowser())
128
+ return;
129
+ const resolved = resolve(pref);
130
+ if (pref === "auto") {
131
+ document.documentElement.removeAttribute(ATTR);
132
+ }
133
+ else {
134
+ document.documentElement.setAttribute(ATTR, resolved);
135
+ }
136
+ for (const cb of listeners) {
137
+ try {
138
+ cb(resolved);
139
+ }
140
+ catch (e) {
141
+ console.error("cobd theming listener", e);
142
+ }
143
+ }
144
+ }
145
+ function attachMediaWatcher() {
146
+ if (!isBrowser() || mediaCleanup)
147
+ return;
148
+ const queries = [
149
+ "(prefers-color-scheme: dark)",
150
+ "(prefers-contrast: more)",
151
+ ];
152
+ const mqs = queries
153
+ .map(q => window.matchMedia?.(q))
154
+ .filter((mq) => Boolean(mq));
155
+ if (mqs.length === 0)
156
+ return;
157
+ const handler = () => {
158
+ if (readPref() === "auto")
159
+ apply("auto");
160
+ };
161
+ for (const mq of mqs)
162
+ mq.addEventListener?.("change", handler);
163
+ mediaCleanup = () => {
164
+ for (const mq of mqs)
165
+ mq.removeEventListener?.("change", handler);
166
+ };
167
+ }
168
+ export function getThemes() {
169
+ return ALL_THEMES.slice();
170
+ }
171
+ export function getThemePreference() {
172
+ return readPref();
173
+ }
174
+ export function getActiveTheme() {
175
+ return resolve(readPref());
176
+ }
177
+ export function setTheme(pref) {
178
+ if (!ALL_THEMES.includes(pref)) {
179
+ throw new Error(`@cobdfamily/clf-core: unknown theme '${pref}'. `
180
+ + `Known: ${ALL_THEMES.join(", ")}`);
181
+ }
182
+ writePref(pref);
183
+ apply(pref);
184
+ }
185
+ export function onThemeChange(cb) {
186
+ listeners.add(cb);
187
+ return () => { listeners.delete(cb); };
188
+ }
189
+ export function initTheming(opts) {
190
+ // initTheming is intentionally idempotent: tear down any
191
+ // prior watcher + reset the backend, so callers can swap
192
+ // storage modes (or reinitialise after a hot-reload)
193
+ // without leaking listeners.
194
+ if (mediaCleanup) {
195
+ mediaCleanup();
196
+ mediaCleanup = null;
197
+ }
198
+ if (opts?.storage === "cookie") {
199
+ backend = cookieBackend(opts.cookieName ?? DEFAULT_STORAGE_KEY, opts.cookieDomain, opts.cookieMaxAgeDays ?? 365);
200
+ fontScaleBackend = makeFontScaleCookie(opts.cookieDomain, opts.cookieMaxAgeDays ?? 365);
201
+ }
202
+ else {
203
+ backend = localStorageBackend(opts?.cookieName ?? DEFAULT_STORAGE_KEY);
204
+ fontScaleBackend = makeFontScaleLocal();
205
+ }
206
+ if (!isBrowser())
207
+ return;
208
+ attachMediaWatcher();
209
+ apply(readPref());
210
+ applyFontScale(readFontScale());
211
+ }
212
+ const FONT_SCALES = {
213
+ sm: 0.9, md: 1.0, lg: 1.15,
214
+ };
215
+ const FONT_SCALE_ATTR = "data-cobd-font-scale";
216
+ const FONT_SCALE_CSS_VAR = "--cobd-font-scale";
217
+ const FONT_SCALE_STORAGE_KEY = "cobd-font-scale";
218
+ function isFontScale(v) {
219
+ return typeof v === "string"
220
+ && (v === "sm" || v === "md" || v === "lg");
221
+ }
222
+ function makeFontScaleLocal() {
223
+ return {
224
+ read() {
225
+ try {
226
+ const v = localStorage.getItem(FONT_SCALE_STORAGE_KEY);
227
+ return isFontScale(v) ? v : null;
228
+ }
229
+ catch {
230
+ return null;
231
+ }
232
+ },
233
+ write(p) {
234
+ try {
235
+ if (p === "md") {
236
+ localStorage.removeItem(FONT_SCALE_STORAGE_KEY);
237
+ }
238
+ else {
239
+ localStorage.setItem(FONT_SCALE_STORAGE_KEY, p);
240
+ }
241
+ }
242
+ catch { /* private mode etc. */ }
243
+ },
244
+ };
245
+ }
246
+ function makeFontScaleCookie(domain, maxAgeDays) {
247
+ function read() {
248
+ if (!isBrowser())
249
+ return null;
250
+ const prefix = FONT_SCALE_STORAGE_KEY + "=";
251
+ for (const part of document.cookie.split(";")) {
252
+ const p = part.trim();
253
+ if (p.startsWith(prefix)) {
254
+ const v = decodeURIComponent(p.slice(prefix.length));
255
+ return isFontScale(v) ? v : null;
256
+ }
257
+ }
258
+ return null;
259
+ }
260
+ function set(value) {
261
+ if (!isBrowser())
262
+ return;
263
+ const parts = [
264
+ `${FONT_SCALE_STORAGE_KEY}=`
265
+ + (value === null ? "" : encodeURIComponent(value)),
266
+ "Path=/",
267
+ "SameSite=Lax",
268
+ ];
269
+ if (location.protocol === "https:")
270
+ parts.push("Secure");
271
+ if (domain)
272
+ parts.push(`Domain=${domain}`);
273
+ if (value === null) {
274
+ parts.push("Max-Age=0");
275
+ }
276
+ else {
277
+ parts.push(`Max-Age=${maxAgeDays * 24 * 60 * 60}`);
278
+ }
279
+ document.cookie = parts.join("; ");
280
+ }
281
+ return {
282
+ read,
283
+ write(p) {
284
+ // md is the default; clear the cookie when chosen
285
+ // so we don't carry empty state.
286
+ p === "md" ? set(null) : set(p);
287
+ },
288
+ };
289
+ }
290
+ // Default backend is localStorage. initTheming swaps to a
291
+ // cookie-backed pair when the consumer asks for cookie
292
+ // storage. Callers using set/getFontScale before
293
+ // initTheming runs get the default.
294
+ let fontScaleBackend = isBrowser()
295
+ ? makeFontScaleLocal()
296
+ : { read: () => null, write: () => undefined };
297
+ function readFontScale() {
298
+ return fontScaleBackend.read() ?? "md";
299
+ }
300
+ function applyFontScale(pref) {
301
+ if (!isBrowser())
302
+ return;
303
+ const root = document.documentElement;
304
+ root.style.setProperty(FONT_SCALE_CSS_VAR, String(FONT_SCALES[pref]));
305
+ root.setAttribute(FONT_SCALE_ATTR, pref);
306
+ for (const cb of fontScaleListeners) {
307
+ try {
308
+ cb(pref);
309
+ }
310
+ catch (e) {
311
+ console.error("cobd font-scale listener", e);
312
+ }
313
+ }
314
+ }
315
+ const fontScaleListeners = new Set();
316
+ export function getFontScales() {
317
+ return ["sm", "md", "lg"];
318
+ }
319
+ export function getFontScalePreference() {
320
+ return readFontScale();
321
+ }
322
+ export function setFontScale(pref) {
323
+ if (!isFontScale(pref)) {
324
+ throw new Error(`@cobdfamily/clf-core: unknown font-scale '${pref}'. `
325
+ + "Known: sm, md, lg");
326
+ }
327
+ fontScaleBackend.write(pref);
328
+ applyFontScale(pref);
329
+ }
330
+ export function onFontScaleChange(cb) {
331
+ fontScaleListeners.add(cb);
332
+ return () => { fontScaleListeners.delete(cb); };
333
+ }