@sorb/leaf 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/TokenProvider.jsx
2
- import React, { useCallback, useEffect, useRef, useState } from "react";
2
+ import React, { useCallback, useEffect, useMemo, useRef } from "react";
3
3
 
4
4
  // src/context.js
5
5
  import { createContext, useContext } from "react";
@@ -92,6 +92,170 @@ var applyTokens = (tokens) => {
92
92
  root.style.setProperty(`--${key}`, result.value);
93
93
  });
94
94
  };
95
+ var clearTokenOverrides = (tokens) => {
96
+ const root = document.documentElement;
97
+ Object.keys(tokens).forEach((key) => {
98
+ root.style.removeProperty(`--${key}`);
99
+ });
100
+ };
101
+ var MODE_STYLESHEET_ID = "sorb-tokens";
102
+ var injectModeStylesheet = (css) => {
103
+ let tag = document.getElementById(MODE_STYLESHEET_ID);
104
+ if (!tag) {
105
+ tag = document.createElement("style");
106
+ tag.id = MODE_STYLESHEET_ID;
107
+ document.head.appendChild(tag);
108
+ }
109
+ tag.textContent = css;
110
+ };
111
+ var clearModeStylesheet = () => {
112
+ const tag = document.getElementById(MODE_STYLESHEET_ID);
113
+ if (tag && tag.parentNode) tag.parentNode.removeChild(tag);
114
+ };
115
+
116
+ // src/modeStylesheet.js
117
+ var buildModeStylesheet = (lightVars, darkVars, darkMode) => {
118
+ const lightDecls = normalizeDecls(lightVars);
119
+ const hasDark = !!darkMode && !!darkVars && Object.keys(darkVars).length > 0;
120
+ if (!hasDark) {
121
+ return `:root {
122
+ ${indent(lightDecls)}
123
+ }
124
+ `;
125
+ }
126
+ const darkDecls = normalizeDecls(darkVars);
127
+ const darkSelector = darkMode.darkSelector;
128
+ const lightSelector = darkMode.lightSelector;
129
+ const mediaScopeSelector = lightSelector ? `:root:not(${lightSelector})` : ":root";
130
+ const lines = [];
131
+ lines.push(":root {");
132
+ lines.push(indent([...lightDecls, "color-scheme: light;"]));
133
+ lines.push("}");
134
+ lines.push("@media (prefers-color-scheme: dark) {");
135
+ lines.push(` ${mediaScopeSelector} {`);
136
+ lines.push(indent([...darkDecls, "color-scheme: dark;"], 2));
137
+ lines.push(" }");
138
+ lines.push("}");
139
+ lines.push(`${darkSelector} {`);
140
+ lines.push(indent([...darkDecls, "color-scheme: dark;"]));
141
+ lines.push("}");
142
+ if (lightSelector) {
143
+ lines.push(`${lightSelector} {`);
144
+ lines.push(indent([...lightDecls, "color-scheme: light;"]));
145
+ lines.push("}");
146
+ }
147
+ return `${lines.join("\n")}
148
+ `;
149
+ };
150
+ var normalizeDecls = (vars) => {
151
+ if (!vars) return [];
152
+ return Object.entries(vars).reduce((acc, [key, value]) => {
153
+ const result = sanitizeCssValue(String(value));
154
+ if (!result.ok) return acc;
155
+ const cssVar = key.startsWith("--") ? key : `--${key}`;
156
+ acc.push(`${cssVar}: ${result.value};`);
157
+ return acc;
158
+ }, []);
159
+ };
160
+ var indent = (lines, level = 1) => {
161
+ const pad = " ".repeat(level);
162
+ return lines.map((l) => `${pad}${l}`).join("\n");
163
+ };
164
+
165
+ // node_modules/@sorb/core/src/index.js
166
+ var TIERS = Object.freeze(["component", "semantic", "primitive"]);
167
+ var TIER_RANK = Object.freeze({ component: 0, semantic: 1, primitive: 2 });
168
+ var DEFAULT_ROLE_IDS = Object.freeze({
169
+ color: Object.freeze([
170
+ "color.surface",
171
+ "color.surface-raised",
172
+ "color.surface-sunken",
173
+ "color.ink",
174
+ "color.ink-muted",
175
+ "color.ink-on-brand",
176
+ "color.brand",
177
+ "color.brand-hover",
178
+ "color.brand-contrast",
179
+ "color.accent",
180
+ "color.accent-hover",
181
+ "color.accent-contrast",
182
+ "color.danger",
183
+ "color.danger-hover",
184
+ "color.success",
185
+ "color.success-hover",
186
+ "color.focus-ring",
187
+ "color.border",
188
+ "color.border-subtle",
189
+ "color.border-strong"
190
+ ]),
191
+ radius: Object.freeze(["radius.control", "radius.card", "radius.pill"]),
192
+ shadow: Object.freeze(["shadow.raised", "shadow.overlay"]),
193
+ typography: Object.freeze([
194
+ "typography.display.fontSize",
195
+ "typography.display.fontWeight",
196
+ "typography.display.lineHeight",
197
+ "typography.heading.fontSize",
198
+ "typography.heading.fontWeight",
199
+ "typography.heading.lineHeight",
200
+ "typography.body.fontSize",
201
+ "typography.body.fontWeight",
202
+ "typography.body.lineHeight",
203
+ "typography.caption.fontSize",
204
+ "typography.caption.fontWeight",
205
+ "typography.caption.lineHeight"
206
+ ])
207
+ });
208
+ var ALL_ROLE_IDS = Object.freeze([
209
+ ...DEFAULT_ROLE_IDS.color,
210
+ ...DEFAULT_ROLE_IDS.radius,
211
+ ...DEFAULT_ROLE_IDS.shadow,
212
+ ...DEFAULT_ROLE_IDS.typography
213
+ ]);
214
+ var connectors = Object.freeze({
215
+ source: /* @__PURE__ */ new Map(),
216
+ codeSource: /* @__PURE__ */ new Map(),
217
+ target: /* @__PURE__ */ new Map()
218
+ });
219
+ function registerTarget(adapter) {
220
+ if (!adapter || typeof adapter.id !== "string" || !adapter.id) {
221
+ throw new Error("registerTarget: adapter.id must be a non-empty string");
222
+ }
223
+ if (typeof adapter.emitFormat !== "string" || !adapter.emitFormat) {
224
+ throw new Error(`registerTarget(${JSON.stringify(adapter.id)}): emitFormat must be a non-empty string`);
225
+ }
226
+ if (!Array.isArray(adapter.expectPrefixes)) {
227
+ throw new Error(`registerTarget(${JSON.stringify(adapter.id)}): expectPrefixes must be an array`);
228
+ }
229
+ if (connectors.target.has(adapter.id)) {
230
+ console.warn(`registerTarget: overwriting existing target adapter ${JSON.stringify(adapter.id)}`);
231
+ }
232
+ connectors.target.set(adapter.id, adapter);
233
+ return adapter;
234
+ }
235
+
236
+ // src/targets/reactBootstrap.js
237
+ var SORB_TOKENSET_FORMAT_ID = "sorb/tokenset-esm";
238
+ var reactBootstrapTarget = {
239
+ id: "react-bootstrap",
240
+ emitFormat: SORB_TOKENSET_FORMAT_ID,
241
+ // The Bootstrap-styled vocab namespace (matches `sorb-demo/src/sorbConfig.js`'s
242
+ // `preview.expectPrefixes: ['bs-']`).
243
+ expectPrefixes: ["bs-"],
244
+ // Left undefined on purpose — see file header.
245
+ inject: void 0,
246
+ // Bootstrap 5.3's native dark-mode convention (real-dark-mode spec D1): a
247
+ // `data-bs-theme` attribute on any ancestor (Bootstrap recommends
248
+ // `<html>`) selects the mode; absent ⇒ OS `prefers-color-scheme` governs.
249
+ darkMode: {
250
+ strategy: "attribute",
251
+ attribute: "data-bs-theme",
252
+ darkSelector: '[data-bs-theme="dark"]',
253
+ lightSelector: '[data-bs-theme="light"]'
254
+ }
255
+ };
256
+ if (typeof registerTarget === "function") {
257
+ registerTarget(reactBootstrapTarget);
258
+ }
95
259
 
96
260
  // src/previewGuard.js
97
261
  var DEFAULT_ORIGIN = "http://localhost:7777";
@@ -134,6 +298,26 @@ var shouldLoadPreview = (config) => {
134
298
  return { allowed: false, origin, reason: "origin-not-allowlisted" };
135
299
  };
136
300
 
301
+ // src/previewVocab.js
302
+ var countMatchingPrefixes = (tokens, prefixes) => {
303
+ const list = Array.isArray(prefixes) ? prefixes : [];
304
+ return Object.keys(tokens || {}).filter((key) => list.some((p) => key.startsWith(p))).length;
305
+ };
306
+ var checkPreviewVocabulary = ({ tokens, expectPrefixes, previewId }) => {
307
+ if (!Array.isArray(expectPrefixes) || expectPrefixes.length === 0) return false;
308
+ const appliedCount = Object.keys(tokens || {}).length;
309
+ if (appliedCount === 0) return false;
310
+ const matched = countMatchingPrefixes(tokens, expectPrefixes);
311
+ if (matched > 0) return false;
312
+ try {
313
+ console.warn(
314
+ `[Sorb] preview ${previewId ? `"${previewId}" ` : ""}applied ${appliedCount} tokens but none match expected prefixes ${JSON.stringify(expectPrefixes)} \u2014 the app may not visibly re-skin (token-vocabulary mismatch).`
315
+ );
316
+ } catch (e) {
317
+ }
318
+ return true;
319
+ };
320
+
137
321
  // src/bridgeAuth.js
138
322
  var bridgeHeaders = (key, base) => {
139
323
  const headers = base ? { ...base } : {};
@@ -143,8 +327,163 @@ var bridgeHeaders = (key, base) => {
143
327
  return headers;
144
328
  };
145
329
 
146
- // src/TokenProvider.jsx
147
- import { jsx } from "react/jsx-runtime";
330
+ // src/connection.js
331
+ var DEFAULT_CLOUD_BASE = "https://api.sorbcloud.com";
332
+ var getOrgKey = (config) => {
333
+ if (!config) return null;
334
+ const key = config.orgKey || config.publishableKey;
335
+ return typeof key === "string" && key.trim() !== "" ? key.trim() : null;
336
+ };
337
+ var shouldResolveOrgConnection = (config) => {
338
+ const key = getOrgKey(config);
339
+ if (!key) return false;
340
+ const explicitOrigin = config && config.preview && config.preview.origin;
341
+ return !(typeof explicitOrigin === "string" && explicitOrigin.trim() !== "");
342
+ };
343
+ var resolveOrgConnection = async (orgKey, opts) => {
344
+ const { cloudBase = DEFAULT_CLOUD_BASE, fetchImpl } = opts || {};
345
+ const doFetch = fetchImpl || (typeof fetch !== "undefined" ? fetch : null);
346
+ if (!doFetch || typeof orgKey !== "string" || orgKey.trim() === "") return null;
347
+ try {
348
+ const base = cloudBase.replace(/\/$/, "");
349
+ const url = `${base}/api/orgs/resolve?key=${encodeURIComponent(orgKey.trim())}`;
350
+ const res = await doFetch(url);
351
+ if (!res || !res.ok) return null;
352
+ const data = await res.json();
353
+ if (!data || typeof data !== "object") return null;
354
+ if (typeof data.bridgeUrl !== "string" || data.bridgeUrl.trim() === "") return null;
355
+ const bridgeMode = typeof data.bridgeMode === "string" ? data.bridgeMode : "C";
356
+ return {
357
+ bridgeMode,
358
+ bridgeUrl: data.bridgeUrl,
359
+ orgId: typeof data.orgId === "string" ? data.orgId : null,
360
+ tokenSource: typeof data.tokenSource === "string" ? data.tokenSource : null,
361
+ // sorb-cloud's /api/orgs/resolve returns this as a boolean (entitlement
362
+ // flag), not an object — see cloud src/lib/orgResolve.ts.
363
+ previewPersistence: typeof data.previewPersistence === "boolean" ? data.previewPersistence : null,
364
+ transport: data.transport === "poll" || data.transport === "sse" ? data.transport : bridgeMode === "A" ? "sse" : "poll"
365
+ };
366
+ } catch (e) {
367
+ return null;
368
+ }
369
+ };
370
+ var buildEffectivePreviewConfig = (config, resolved) => {
371
+ const base = config && config.preview || {};
372
+ if (!resolved) return base;
373
+ const orgKey = getOrgKey(config);
374
+ return {
375
+ ...base,
376
+ enabled: true,
377
+ origin: resolved.bridgeUrl,
378
+ allowedOrigins: [...Array.isArray(base.allowedOrigins) ? base.allowedOrigins : [], resolved.bridgeUrl],
379
+ key: base.key || orgKey || void 0
380
+ };
381
+ };
382
+ var buildEffectiveConfig = (config, resolved) => {
383
+ if (!resolved) return config;
384
+ return { ...config, preview: buildEffectivePreviewConfig(config, resolved) };
385
+ };
386
+
387
+ // src/sse.js
388
+ var buildSubscribeUrl = (bridgeUrl, orgId, previewId, key) => {
389
+ const base = String(bridgeUrl).replace(/\/$/, "");
390
+ const path = `${base}/orgs/${encodeURIComponent(orgId)}/preview/${encodeURIComponent(previewId)}/subscribe`;
391
+ if (typeof key === "string" && key.trim() !== "") {
392
+ return `${path}?key=${encodeURIComponent(key.trim())}`;
393
+ }
394
+ return path;
395
+ };
396
+ var parsePreviewFrame = (raw) => {
397
+ let frame;
398
+ try {
399
+ frame = JSON.parse(raw);
400
+ } catch (e) {
401
+ return null;
402
+ }
403
+ if (!frame || typeof frame !== "object") return null;
404
+ if (frame.type === "ping") return { type: "ping" };
405
+ if (frame.type === "delete") return { type: "delete", tokens: null };
406
+ if ((frame.type === "snapshot" || frame.type === "update") && frame.tokens && typeof frame.tokens === "object") {
407
+ return { type: frame.type, tokens: frame.tokens };
408
+ }
409
+ return null;
410
+ };
411
+ var createPreviewSubscription = ({ EventSourceImpl, url, onTokens, onDelete, onError }) => {
412
+ if (typeof EventSourceImpl !== "function") return null;
413
+ const es = new EventSourceImpl(url);
414
+ es.onmessage = (evt) => {
415
+ const parsed = parsePreviewFrame(evt && evt.data);
416
+ if (!parsed || parsed.type === "ping") return;
417
+ if (parsed.type === "delete") {
418
+ if (onDelete) onDelete();
419
+ return;
420
+ }
421
+ onTokens(parsed.tokens);
422
+ };
423
+ const handleError = (evt) => {
424
+ if (onError) onError(evt);
425
+ };
426
+ if (typeof es.addEventListener === "function") {
427
+ es.addEventListener("error", handleError);
428
+ } else {
429
+ es.onerror = handleError;
430
+ }
431
+ return () => {
432
+ try {
433
+ es.close();
434
+ } catch (e) {
435
+ }
436
+ };
437
+ };
438
+
439
+ // src/previewMode.js
440
+ var isModeAwarePreviewBody = (body) => !!body && typeof body === "object" && !Array.isArray(body) && "tokens" in body;
441
+ var resolvePreviewBody = (body, fallbackDarkMode) => {
442
+ if (!isModeAwarePreviewBody(body)) {
443
+ return { kind: "flat", tokens: (
444
+ /** @type {import('./types').TokenSet} */
445
+ body
446
+ ) };
447
+ }
448
+ const wrapper = (
449
+ /** @type {{ tokens: import('./types').TokenSet, darkTokens?: import('./types').TokenSet, darkMode?: import('@sorb/core').DarkModeConvention }} */
450
+ body
451
+ );
452
+ const darkTokens = wrapper.darkTokens;
453
+ if (darkTokens && Object.keys(darkTokens).length > 0) {
454
+ return {
455
+ kind: "mode-aware",
456
+ lightTokens: wrapper.tokens,
457
+ darkTokens,
458
+ darkMode: wrapper.darkMode ?? fallbackDarkMode
459
+ };
460
+ }
461
+ return { kind: "flat", tokens: wrapper.tokens };
462
+ };
463
+
464
+ // src/modeAction.js
465
+ var darkClassName = (darkModeConvention) => {
466
+ const selector = String(darkModeConvention?.darkSelector || "").trim();
467
+ const match = /^\.([a-zA-Z0-9_-]+)$/.exec(selector);
468
+ return match ? match[1] : "dark";
469
+ };
470
+ var resolveModeAction = (darkModeConvention, next) => {
471
+ const strategy = darkModeConvention?.strategy || "attribute";
472
+ if (strategy === "media") {
473
+ return { type: "none" };
474
+ }
475
+ if (strategy === "class") {
476
+ const className = darkClassName(darkModeConvention);
477
+ return next === "dark" ? { type: "class-add", className } : { type: "class-remove", className };
478
+ }
479
+ const attribute = darkModeConvention?.attribute || "data-bs-theme";
480
+ return next === "auto" ? { type: "attr-remove", attribute } : { type: "attr-set", attribute, value: next };
481
+ };
482
+
483
+ // src/core.js
484
+ var EventSourceCtor = typeof EventSource !== "undefined" ? EventSource : null;
485
+ var matchMediaFn = typeof matchMedia !== "undefined" ? matchMedia : null;
486
+ var DARK_MEDIA_QUERY = "(prefers-color-scheme: dark)";
148
487
  var devWarn = (msg) => {
149
488
  try {
150
489
  if (typeof process !== "undefined" && process.env && true) {
@@ -153,54 +492,187 @@ var devWarn = (msg) => {
153
492
  } catch (e) {
154
493
  }
155
494
  };
156
- var SorbProvider = ({ config, children }) => {
157
- const [activeTokens, setActiveTokens] = useState(config.tokens);
158
- const [isPreview, setIsPreview] = useState(false);
159
- const [previewId, setPreviewId] = useState(null);
160
- const pollRef = useRef(null);
161
- const loadCommitted = useCallback(() => {
162
- applyTokens(config.tokens);
163
- setActiveTokens(config.tokens);
164
- setIsPreview(false);
165
- setPreviewId(null);
166
- }, [config.tokens]);
167
- const loadPreview = useCallback(
168
- async (id) => {
169
- const guard = shouldLoadPreview(config);
170
- if (!guard.allowed) {
171
- loadCommitted();
172
- return false;
173
- }
174
- const origin = guard.origin;
175
- try {
176
- const res = await fetch(`${origin}/preview/${id}`, {
177
- headers: bridgeHeaders(config.preview?.key)
178
- });
179
- if (!res.ok) throw new Error("preview not found");
180
- const tokens = await res.json();
181
- applyTokens(tokens);
182
- setActiveTokens(tokens);
183
- setIsPreview(true);
184
- setPreviewId(id);
185
- return true;
186
- } catch (e) {
187
- loadCommitted();
188
- return false;
189
- }
190
- },
191
- [config, loadCommitted]
192
- );
193
- const clearPreview = useCallback(() => {
194
- if (pollRef.current) clearInterval(pollRef.current);
195
- const params = new URLSearchParams(location.search);
196
- params.delete("preview");
197
- const qs = params.toString();
198
- history.replaceState(null, "", qs ? `?${qs}` : location.pathname);
495
+ var warnedDeprecations = /* @__PURE__ */ new Set();
496
+ function warnDeprecated(resolved) {
497
+ if (typeof process !== "undefined" && false) return;
498
+ for (let i = 0; i < resolved.length; i++) {
499
+ const token = resolved[i];
500
+ if (!token.deprecated) continue;
501
+ if (warnedDeprecations.has(token.id)) continue;
502
+ warnedDeprecations.add(token.id);
503
+ const replacedBy = token.replacedBy || token.$extensions && token.$extensions.sorb && token.$extensions.sorb.replacedBy || null;
504
+ if (replacedBy) {
505
+ console.warn("[@sorb/leaf] Deprecated token: " + token.id + " \u2014 use " + replacedBy + " instead");
506
+ } else {
507
+ console.warn("[@sorb/leaf] Deprecated token: " + token.id + " is deprecated");
508
+ }
509
+ }
510
+ }
511
+ function sorbInit(config) {
512
+ let activeTokens = config.tokens;
513
+ let isPreview = false;
514
+ let previewId = null;
515
+ let previewMismatch = false;
516
+ let pollId = null;
517
+ let cancelled = false;
518
+ let unsubscribeSSE = null;
519
+ const hasDarkMode = !!(config.darkTokens && Object.keys(config.darkTokens).length > 0);
520
+ const darkModeConvention = config.darkModeConvention || reactBootstrapTarget.darkMode;
521
+ let mode = "auto";
522
+ let systemScheme = matchMediaFn ? matchMediaFn(DARK_MEDIA_QUERY).matches ? "dark" : "light" : "light";
523
+ const listeners = /* @__PURE__ */ new Set();
524
+ const getState = () => ({
525
+ tokens: activeTokens,
526
+ isPreview,
527
+ previewId,
528
+ previewMismatch,
529
+ mode,
530
+ resolvedScheme: mode === "auto" ? systemScheme : mode
531
+ });
532
+ const notify = () => {
533
+ const state = getState();
534
+ listeners.forEach((listener) => listener(state));
535
+ };
536
+ const subscribe = (listener) => {
537
+ listeners.add(listener);
538
+ return () => listeners.delete(listener);
539
+ };
540
+ let mql = null;
541
+ const onSchemeChange = (e) => {
542
+ systemScheme = e.matches ? "dark" : "light";
543
+ notify();
544
+ };
545
+ if (matchMediaFn) {
546
+ mql = matchMediaFn(DARK_MEDIA_QUERY);
547
+ if (typeof mql.addEventListener === "function") mql.addEventListener("change", onSchemeChange);
548
+ else if (typeof mql.addListener === "function") mql.addListener(onSchemeChange);
549
+ }
550
+ let inlineTokens = null;
551
+ const applyFlat = (tokens) => {
552
+ clearModeStylesheet();
553
+ applyTokens(tokens);
554
+ inlineTokens = tokens;
555
+ };
556
+ const applyModeAware = (lightTokens, darkTokens, convention) => {
557
+ if (inlineTokens) {
558
+ clearTokenOverrides(inlineTokens);
559
+ inlineTokens = null;
560
+ }
561
+ injectModeStylesheet(buildModeStylesheet(lightTokens, darkTokens, convention));
562
+ };
563
+ const loadCommitted = () => {
564
+ if (hasDarkMode) {
565
+ applyModeAware(config.tokens, config.darkTokens, darkModeConvention);
566
+ } else {
567
+ applyFlat(config.tokens);
568
+ }
569
+ activeTokens = config.tokens;
570
+ isPreview = false;
571
+ previewId = null;
572
+ previewMismatch = false;
573
+ notify();
574
+ };
575
+ const applyPreviewTokens = (body, id, effectiveConfig) => {
576
+ const resolved = resolvePreviewBody(body, darkModeConvention);
577
+ let flatTokens;
578
+ if (resolved.kind === "mode-aware") {
579
+ applyModeAware(resolved.lightTokens, resolved.darkTokens, resolved.darkMode);
580
+ flatTokens = resolved.lightTokens;
581
+ } else {
582
+ applyFlat(resolved.tokens);
583
+ flatTokens = resolved.tokens;
584
+ }
585
+ activeTokens = flatTokens;
586
+ isPreview = true;
587
+ previewId = id;
588
+ previewMismatch = checkPreviewVocabulary({
589
+ tokens: flatTokens,
590
+ expectPrefixes: effectiveConfig.preview?.expectPrefixes,
591
+ previewId: id
592
+ });
593
+ notify();
594
+ };
595
+ const loadPreview = async (id, effectiveConfig) => {
596
+ const cfg = effectiveConfig || config;
597
+ const guard = shouldLoadPreview(cfg);
598
+ if (!guard.allowed) {
599
+ loadCommitted();
600
+ return false;
601
+ }
602
+ const origin = guard.origin;
603
+ try {
604
+ const res = await fetch(`${origin}/preview/${id}`, {
605
+ headers: bridgeHeaders(cfg.preview?.key)
606
+ });
607
+ if (!res.ok) throw new Error("preview not found");
608
+ const tokens = await res.json();
609
+ applyPreviewTokens(tokens, id, cfg);
610
+ return true;
611
+ } catch (e) {
612
+ loadCommitted();
613
+ return false;
614
+ }
615
+ };
616
+ const clearPreview = () => {
617
+ if (pollId) {
618
+ clearInterval(pollId);
619
+ pollId = null;
620
+ }
621
+ if (typeof location !== "undefined" && typeof history !== "undefined") {
622
+ const params = new URLSearchParams(location.search);
623
+ params.delete("preview");
624
+ const qs = params.toString();
625
+ history.replaceState(null, "", qs ? `?${qs}` : location.pathname);
626
+ }
199
627
  loadCommitted();
200
- }, [loadCommitted]);
201
- useEffect(() => {
202
- const guard = shouldLoadPreview(config);
203
- const id = new URLSearchParams(location.search).get("preview");
628
+ };
629
+ const setMode = (next) => {
630
+ mode = next;
631
+ if (typeof document !== "undefined") {
632
+ const action = resolveModeAction(darkModeConvention, next);
633
+ switch (action.type) {
634
+ case "attr-set":
635
+ document.documentElement.setAttribute(action.attribute, action.value);
636
+ break;
637
+ case "attr-remove":
638
+ document.documentElement.removeAttribute(action.attribute);
639
+ break;
640
+ case "class-add":
641
+ document.documentElement.classList.add(action.className);
642
+ break;
643
+ case "class-remove":
644
+ document.documentElement.classList.remove(action.className);
645
+ break;
646
+ case "none":
647
+ default:
648
+ break;
649
+ }
650
+ }
651
+ notify();
652
+ };
653
+ const destroy = () => {
654
+ cancelled = true;
655
+ if (pollId) clearInterval(pollId);
656
+ if (unsubscribeSSE) unsubscribeSSE();
657
+ if (mql) {
658
+ if (typeof mql.removeEventListener === "function") mql.removeEventListener("change", onSchemeChange);
659
+ else if (typeof mql.removeListener === "function") mql.removeListener(onSchemeChange);
660
+ }
661
+ listeners.clear();
662
+ };
663
+ const init = async () => {
664
+ if (config.resolved && config.resolved.length) warnDeprecated(config.resolved);
665
+ let effectiveConfig = config;
666
+ let resolvedConnection = null;
667
+ if (shouldResolveOrgConnection(config)) {
668
+ resolvedConnection = await resolveOrgConnection(getOrgKey(config), {
669
+ cloudBase: config.cloudBase
670
+ });
671
+ if (cancelled) return;
672
+ effectiveConfig = buildEffectiveConfig(config, resolvedConnection);
673
+ }
674
+ const guard = shouldLoadPreview(effectiveConfig);
675
+ const id = typeof location !== "undefined" ? new URLSearchParams(location.search).get("preview") : null;
204
676
  if (!guard.allowed || !id) {
205
677
  if (id && !guard.allowed) {
206
678
  devWarn(
@@ -210,16 +682,213 @@ var SorbProvider = ({ config, children }) => {
210
682
  loadCommitted();
211
683
  return;
212
684
  }
213
- loadPreview(id).then((ok) => {
214
- if (!ok) return;
215
- const interval = config.preview?.pollInterval ?? 1500;
216
- pollRef.current = setInterval(() => loadPreview(id), interval);
217
- });
685
+ const ok = await loadPreview(id, effectiveConfig);
686
+ if (!ok || cancelled) return;
687
+ const useSSE = resolvedConnection && resolvedConnection.transport === "sse" && resolvedConnection.orgId && EventSourceCtor;
688
+ if (useSSE) {
689
+ const url = buildSubscribeUrl(
690
+ resolvedConnection.bridgeUrl,
691
+ resolvedConnection.orgId,
692
+ id,
693
+ effectiveConfig.preview?.key
694
+ );
695
+ unsubscribeSSE = createPreviewSubscription({
696
+ EventSourceImpl: EventSourceCtor,
697
+ url,
698
+ onTokens: (tokens) => applyPreviewTokens(tokens, id, effectiveConfig),
699
+ onDelete: () => loadCommitted(),
700
+ onError: () => devWarn("SSE preview subscription error \u2014 preview may be stale")
701
+ });
702
+ }
703
+ if (!unsubscribeSSE) {
704
+ const interval = effectiveConfig.preview?.pollInterval ?? 1500;
705
+ pollId = setInterval(() => loadPreview(id, effectiveConfig), interval);
706
+ }
707
+ };
708
+ init();
709
+ return { getState, subscribe, setMode, clearPreview, destroy };
710
+ }
711
+
712
+ // src/legacyMap.js
713
+ var normalizeProp = (prop) => {
714
+ if (typeof prop !== "string") return "";
715
+ return prop.trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/_/g, "-").toLowerCase();
716
+ };
717
+ var canonicalizeColor = (v) => {
718
+ const hex = v.match(/^#([0-9a-f]{3,8})$/);
719
+ if (hex) {
720
+ let h = hex[1];
721
+ if (h.length === 3 || h.length === 4) {
722
+ h = h.split("").map((c) => c + c).join("");
723
+ }
724
+ if (h.length !== 6 && h.length !== 8) return null;
725
+ const r = parseInt(h.slice(0, 2), 16);
726
+ const g = parseInt(h.slice(2, 4), 16);
727
+ const b = parseInt(h.slice(4, 6), 16);
728
+ if (h.length === 8) {
729
+ const a = parseInt(h.slice(6, 8), 16) / 255;
730
+ const as = String(Math.round(a * 1e3) / 1e3);
731
+ return `rgba(${r}, ${g}, ${b}, ${as})`;
732
+ }
733
+ return `rgb(${r}, ${g}, ${b})`;
734
+ }
735
+ const fn = v.match(/^(rgba?)\(([^)]*)\)$/);
736
+ if (fn) {
737
+ const parts = fn[2].split(",").map((p) => p.trim()).filter((p) => p !== "");
738
+ if (parts.length === 3) return `rgb(${parts[0]}, ${parts[1]}, ${parts[2]})`;
739
+ if (parts.length === 4) return `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${parts[3]})`;
740
+ }
741
+ return null;
742
+ };
743
+ var normalizeValue = (value) => {
744
+ if (value == null) return "";
745
+ let v = String(value).trim().toLowerCase();
746
+ if (v === "") return "";
747
+ v = v.replace(/\s+/g, " ");
748
+ const color = canonicalizeColor(v);
749
+ if (color) return color;
750
+ if (/^-?\d*\.?\d+$/.test(v)) v = `${v}px`;
751
+ return v;
752
+ };
753
+ var indexLegacyMap = (legacyMap) => {
754
+ const idx = /* @__PURE__ */ new Map();
755
+ if (!Array.isArray(legacyMap)) return idx;
756
+ for (const row of legacyMap) {
757
+ if (!row || row.cssVar == null || row.raw == null || row.prop == null) continue;
758
+ const p = normalizeProp(row.prop);
759
+ const entry = {
760
+ normValue: normalizeValue(row.raw),
761
+ cssVar: String(row.cssVar).replace(/^--/, ""),
762
+ raw: String(row.raw)
763
+ };
764
+ const list = idx.get(p);
765
+ if (list) list.push(entry);
766
+ else idx.set(p, [entry]);
767
+ }
768
+ return idx;
769
+ };
770
+ var computeLegacyOverride = (prop, computedValue, legacyMap) => {
771
+ const idx = legacyMap instanceof Map ? legacyMap : indexLegacyMap(legacyMap);
772
+ const list = idx.get(normalizeProp(prop));
773
+ if (!list || list.length === 0) return null;
774
+ const target = normalizeValue(computedValue);
775
+ if (target === "") return null;
776
+ for (const entry of list) {
777
+ if (entry.normValue === target) {
778
+ return `var(--${entry.cssVar}, ${entry.raw})`;
779
+ }
780
+ }
781
+ return null;
782
+ };
783
+
784
+ // src/legacyDom.js
785
+ var applyLegacyMap = (root, legacyMap) => {
786
+ const restores = [];
787
+ const handle = { restores };
788
+ if (typeof document === "undefined") return handle;
789
+ const start = root ?? document.body;
790
+ if (!start || !Array.isArray(legacyMap) || legacyMap.length === 0) return handle;
791
+ const idx = indexLegacyMap(legacyMap);
792
+ if (idx.size === 0) return handle;
793
+ const props = Array.from(idx.keys());
794
+ const getView = () => {
795
+ const doc = start.ownerDocument || (start.nodeType === 9 ? start : document);
796
+ return doc.defaultView || (typeof window !== "undefined" ? window : null);
797
+ };
798
+ const view = getView();
799
+ if (!view || typeof view.getComputedStyle !== "function") return handle;
800
+ const visit = (el) => {
801
+ if (!el || el.nodeType !== 1) return;
802
+ const cs = view.getComputedStyle(el);
803
+ for (const prop of props) {
804
+ const computed = cs.getPropertyValue(prop);
805
+ const override = computeLegacyOverride(prop, computed, idx);
806
+ if (override == null) continue;
807
+ const prev = el.style.getPropertyValue(prop);
808
+ restores.push({ el: (
809
+ /** @type {HTMLElement} */
810
+ el
811
+ ), prop, prev });
812
+ el.style.setProperty(prop, override);
813
+ }
814
+ };
815
+ if (start.nodeType === 1) visit(
816
+ /** @type {Element} */
817
+ start
818
+ );
819
+ const all = start.querySelectorAll ? start.querySelectorAll("*") : [];
820
+ for (const el of all) visit(el);
821
+ return handle;
822
+ };
823
+ var clearLegacyMap = (handle) => {
824
+ if (!handle || !Array.isArray(handle.restores)) return;
825
+ for (const { el, prop, prev } of handle.restores) {
826
+ if (!el || !el.style) continue;
827
+ if (prev === "" || prev == null) el.style.removeProperty(prop);
828
+ else el.style.setProperty(prop, prev);
829
+ }
830
+ handle.restores = [];
831
+ };
832
+
833
+ // src/TokenProvider.jsx
834
+ import { jsx } from "react/jsx-runtime";
835
+ var matchMediaFn2 = typeof matchMedia !== "undefined" ? matchMedia : null;
836
+ var DARK_MEDIA_QUERY2 = "(prefers-color-scheme: dark)";
837
+ var SorbProvider = ({ config, legacyMap, children }) => {
838
+ const instanceRef = useRef(null);
839
+ const legacyHandleRef = useRef(null);
840
+ const [state, setState] = React.useState(() => ({
841
+ tokens: config.tokens,
842
+ isPreview: false,
843
+ previewId: null,
844
+ previewMismatch: false,
845
+ mode: "auto",
846
+ resolvedScheme: matchMediaFn2 ? matchMediaFn2(DARK_MEDIA_QUERY2).matches ? "dark" : "light" : "light"
847
+ }));
848
+ const resolvedLegacyMap = legacyMap ?? config.legacyMap ?? null;
849
+ useEffect(() => {
850
+ const instance = sorbInit(config);
851
+ instanceRef.current = instance;
852
+ setState(instance.getState());
853
+ const unsubscribe = instance.subscribe(setState);
854
+ return () => {
855
+ unsubscribe();
856
+ instance.destroy();
857
+ instanceRef.current = null;
858
+ };
859
+ }, []);
860
+ useEffect(() => {
861
+ if (!resolvedLegacyMap || resolvedLegacyMap.length === 0) return void 0;
862
+ if (typeof document === "undefined") return void 0;
863
+ if (legacyHandleRef.current) clearLegacyMap(legacyHandleRef.current);
864
+ legacyHandleRef.current = applyLegacyMap(document.body, resolvedLegacyMap);
218
865
  return () => {
219
- if (pollRef.current) clearInterval(pollRef.current);
866
+ if (legacyHandleRef.current) {
867
+ clearLegacyMap(legacyHandleRef.current);
868
+ legacyHandleRef.current = null;
869
+ }
220
870
  };
871
+ }, [resolvedLegacyMap, state.tokens]);
872
+ const setMode = useCallback((next) => {
873
+ if (instanceRef.current) instanceRef.current.setMode(next);
874
+ }, []);
875
+ const clearPreview = useCallback(() => {
876
+ if (instanceRef.current) instanceRef.current.clearPreview();
221
877
  }, []);
222
- return /* @__PURE__ */ jsx(TokenContext.Provider, { value: { tokens: activeTokens, isPreview, previewId, clearPreview }, children });
878
+ const value = useMemo(
879
+ () => ({
880
+ tokens: state.tokens,
881
+ isPreview: state.isPreview,
882
+ previewId: state.previewId,
883
+ previewMismatch: state.previewMismatch,
884
+ clearPreview,
885
+ mode: state.mode,
886
+ setMode,
887
+ resolvedScheme: state.resolvedScheme
888
+ }),
889
+ [state, clearPreview, setMode]
890
+ );
891
+ return /* @__PURE__ */ jsx(TokenContext.Provider, { value, children });
223
892
  };
224
893
 
225
894
  // src/PreviewBanner.jsx
@@ -241,15 +910,21 @@ var useIsPreview = () => {
241
910
  return useTokenContext().isPreview;
242
911
  };
243
912
  var usePreviewState = () => {
244
- const { isPreview, previewId, clearPreview } = useTokenContext();
245
- return { isPreview, previewId, clearPreview };
913
+ const { isPreview, previewId, previewMismatch, clearPreview } = useTokenContext();
914
+ return { isPreview, previewId, previewMismatch, clearPreview };
915
+ };
916
+ var useTheme = () => {
917
+ const { mode, setMode, resolvedScheme } = useTokenContext();
918
+ return { mode, setMode, resolvedScheme };
246
919
  };
247
920
 
248
921
  // src/PreviewBanner.jsx
249
922
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
250
923
  var PreviewBanner = () => {
251
- const { isPreview, previewId, clearPreview } = usePreviewState();
924
+ const { isPreview, previewId, previewMismatch, clearPreview } = usePreviewState();
252
925
  if (!isPreview) return null;
926
+ const background = previewMismatch ? "var(--sorb-preview-warning-bg, #B54708)" : "#3B5BDB";
927
+ const accent = previewMismatch ? "var(--sorb-preview-warning-accent, #F59E0B)" : "transparent";
253
928
  return /* @__PURE__ */ jsxs(
254
929
  "div",
255
930
  {
@@ -260,7 +935,8 @@ var PreviewBanner = () => {
260
935
  bottom: 0,
261
936
  left: 0,
262
937
  right: 0,
263
- background: "#3B5BDB",
938
+ background,
939
+ borderTop: `3px solid ${accent}`,
264
940
  color: "#fff",
265
941
  padding: "10px 20px",
266
942
  display: "flex",
@@ -275,7 +951,7 @@ var PreviewBanner = () => {
275
951
  },
276
952
  children: [
277
953
  /* @__PURE__ */ jsxs("span", { children: [
278
- /* @__PURE__ */ jsx2("strong", { style: { fontWeight: 600 }, children: "Sorb preview active" }),
954
+ /* @__PURE__ */ jsx2("strong", { style: { fontWeight: 600 }, children: previewMismatch ? "Sorb preview active \u2014 may not re-skin" : "Sorb preview active" }),
279
955
  previewId && /* @__PURE__ */ jsx2(
280
956
  "code",
281
957
  {
@@ -290,7 +966,7 @@ var PreviewBanner = () => {
290
966
  children: previewId
291
967
  }
292
968
  ),
293
- /* @__PURE__ */ jsx2("span", { style: { marginLeft: "8px", opacity: 0.75, fontSize: "12px" }, children: "Token changes from Figma are live" })
969
+ /* @__PURE__ */ jsx2("span", { style: { marginLeft: "8px", opacity: 0.75, fontSize: "12px" }, children: previewMismatch ? "No matching tokens for this app \u2014 colours may be unchanged" : "Token changes from Figma are live" })
294
970
  ] }),
295
971
  /* @__PURE__ */ jsx2(
296
972
  "button",
@@ -318,6 +994,55 @@ var PreviewBanner = () => {
318
994
  );
319
995
  };
320
996
 
997
+ // src/ThemeToggle.jsx
998
+ import React3 from "react";
999
+ import { jsx as jsx3 } from "react/jsx-runtime";
1000
+ var OPTIONS = [
1001
+ { value: "light", label: "Light" },
1002
+ { value: "dark", label: "Dark" },
1003
+ { value: "auto", label: "Auto" }
1004
+ ];
1005
+ var ThemeToggle = ({ className } = {}) => {
1006
+ const { mode, setMode } = useTheme();
1007
+ return /* @__PURE__ */ jsx3(
1008
+ "div",
1009
+ {
1010
+ role: "radiogroup",
1011
+ "aria-label": "Color mode",
1012
+ className,
1013
+ style: {
1014
+ display: "inline-flex",
1015
+ gap: "4px",
1016
+ fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
1017
+ fontSize: "13px"
1018
+ },
1019
+ children: OPTIONS.map(({ value, label }) => {
1020
+ const active = mode === value;
1021
+ return /* @__PURE__ */ jsx3(
1022
+ "button",
1023
+ {
1024
+ type: "button",
1025
+ role: "radio",
1026
+ "aria-checked": active,
1027
+ onClick: () => setMode(value),
1028
+ style: {
1029
+ padding: "4px 10px",
1030
+ borderRadius: "6px",
1031
+ border: "1px solid rgba(0,0,0,0.15)",
1032
+ background: active ? "var(--sorb-theme-toggle-active-bg, #3B5BDB)" : "transparent",
1033
+ color: active ? "#fff" : "inherit",
1034
+ cursor: "pointer",
1035
+ fontWeight: active ? 600 : 400
1036
+ },
1037
+ children: label
1038
+ },
1039
+ value
1040
+ );
1041
+ })
1042
+ }
1043
+ );
1044
+ };
1045
+
321
1046
  // src/verify.js
322
1047
  var toCssVar = (name) => {
323
1048
  const s = String(name).trim();
@@ -362,12 +1087,207 @@ var verifyResolved = async (tokens, { origin = "http://localhost:7777", key, fet
362
1087
  return { ok: false, reason: "bridge-unreachable", error: e && e.message };
363
1088
  }
364
1089
  };
1090
+
1091
+ // src/darkModeConventions.js
1092
+ var tailwindDarkMode = {
1093
+ strategy: "class",
1094
+ darkSelector: ".dark"
1095
+ };
1096
+ var dataThemeDarkMode = {
1097
+ strategy: "attribute",
1098
+ attribute: "data-theme",
1099
+ darkSelector: '[data-theme="dark"]',
1100
+ lightSelector: '[data-theme="light"]'
1101
+ };
1102
+
1103
+ // src/targets/mantine.js
1104
+ var SORB_MANTINE_VARS_FORMAT_ID = "sorb/mantine-vars";
1105
+ var mantineTarget = {
1106
+ id: "mantine",
1107
+ emitFormat: SORB_MANTINE_VARS_FORMAT_ID,
1108
+ // Kit-vocab expectPrefixes (field-correction, non-negotiable): the
1109
+ // payload-side kit namespace, NEVER the framework's own `mantine-`
1110
+ // var prefix — a framework-prefix guard false-positives on every working
1111
+ // preview (verified P2/P3 across the demo program). Overridable via
1112
+ // `config.preview.expectPrefixes`.
1113
+ expectPrefixes: ["color-", "button-", "radius-"],
1114
+ // Left undefined on purpose — see file header.
1115
+ inject: void 0,
1116
+ // CONVENTION-DECLARED, NOT demo-verified — the Mantine JJ demo never built
1117
+ // dark mode (acid-wash is a variant push, not a mode). Mantine v7's
1118
+ // documented color-scheme convention sets a `data-mantine-color-scheme`
1119
+ // attribute (MantineProvider manages it; `useMantineColorScheme` /
1120
+ // `<ColorSchemeScript>` toggle it). Feeds the real-dark-mode program's D1
1121
+ // phase; verification lands there, not here.
1122
+ darkMode: {
1123
+ strategy: "attribute",
1124
+ attribute: "data-mantine-color-scheme",
1125
+ darkSelector: '[data-mantine-color-scheme="dark"]',
1126
+ lightSelector: '[data-mantine-color-scheme="light"]'
1127
+ }
1128
+ };
1129
+ if (typeof registerTarget === "function") {
1130
+ registerTarget(mantineTarget);
1131
+ }
1132
+
1133
+ // src/targets/tailwindV4.js
1134
+ var SORB_TAILWIND_FORMAT_ID = "sorb/tailwind-theme";
1135
+ var tailwindV4Target = {
1136
+ id: "tailwind-v4",
1137
+ emitFormat: SORB_TAILWIND_FORMAT_ID,
1138
+ // Payload-side KIT vocabulary (framework-targets-productization field-
1139
+ // correction), never a framework var prefix — a plain Sorb kit's own
1140
+ // token-family prefixes, matching what `@theme inline` references.
1141
+ expectPrefixes: ["color-", "radius-", "space-", "font-"],
1142
+ // Left undefined on purpose — see file header.
1143
+ inject: void 0,
1144
+ // CONVENTION-DECLARED, not demo-verified (the JJ demos never built dark
1145
+ // mode): Tailwind's documented `darkMode: 'class'` convention — a `.dark`
1146
+ // class toggled on `documentElement`. Feeds the real-dark-mode program's D1
1147
+ // phase; verification lands there.
1148
+ darkMode: tailwindDarkMode
1149
+ };
1150
+ if (typeof registerTarget === "function") {
1151
+ registerTarget(tailwindV4Target);
1152
+ }
1153
+
1154
+ // src/targets/shadcn.js
1155
+ var SORB_SHADCN_FORMAT_ID = "sorb/shadcn-theme";
1156
+ var shadcnTarget = {
1157
+ id: "shadcn",
1158
+ emitFormat: SORB_SHADCN_FORMAT_ID,
1159
+ // Payload-side KIT vocabulary (framework-targets-productization field-
1160
+ // correction), never a framework/shadcn var prefix — shadcn's own vars
1161
+ // (`--background`, `--primary`, …) are the OUTPUT of this format, not the
1162
+ // guarded payload; the guard is against the underlying Sorb kit vocab the
1163
+ // format's :root map references.
1164
+ expectPrefixes: ["color-", "radius-", "space-", "font-"],
1165
+ // Left undefined on purpose — see file header.
1166
+ inject: void 0,
1167
+ // CONVENTION-DECLARED, not demo-verified (the JJ demos never built dark
1168
+ // mode): shadcn ships on top of Tailwind's `darkMode: 'class'` convention
1169
+ // (a `.dark` class toggled on `documentElement`, per shadcn/ui's own
1170
+ // docs). Feeds the real-dark-mode program's D1 phase; verification lands
1171
+ // there.
1172
+ darkMode: tailwindDarkMode
1173
+ };
1174
+ if (typeof registerTarget === "function") {
1175
+ registerTarget(shadcnTarget);
1176
+ }
1177
+
1178
+ // src/targets/primevue.js
1179
+ var SORB_PRIMEVUE_PRESET_FORMAT_ID = "sorb/primevue-preset";
1180
+ var primevueTarget = {
1181
+ id: "primevue",
1182
+ emitFormat: SORB_PRIMEVUE_PRESET_FORMAT_ID,
1183
+ // Kit-vocab expectPrefixes (field-correction, non-negotiable): the
1184
+ // payload-side kit namespace, NEVER a PrimeVue-owned prefix (`p-`) — a
1185
+ // framework-prefix guard false-positives on every working preview
1186
+ // (verified P2/P3 across the demo program). This target's preset draws
1187
+ // from a wider slice of the kit vocab than Mantine/MUI (component-tier
1188
+ // overrides for Tag/Toast/Menubar), hence the longer list. Overridable via
1189
+ // `config.preview.expectPrefixes`.
1190
+ expectPrefixes: ["color-", "button-", "card-", "badge-", "input-", "nav-", "toast-", "radius-"],
1191
+ // Left undefined on purpose — see file header.
1192
+ inject: void 0,
1193
+ // CONVENTION-DECLARED, NOT demo-verified — the PrimeVue JJ demo never
1194
+ // built dark mode (acid-wash is a variant push, not a mode). PrimeVue v4's
1195
+ // documented dark-mode convention is a `.p-dark` selector class (default
1196
+ // `darkModeSelector` in `definePreset`/PrimeVue config), toggled on an
1197
+ // ancestor (typically `<html>`). Feeds the real-dark-mode program's D1
1198
+ // phase; verification lands there, not here.
1199
+ darkMode: {
1200
+ strategy: "class",
1201
+ darkSelector: ".p-dark"
1202
+ }
1203
+ };
1204
+ if (typeof registerTarget === "function") {
1205
+ registerTarget(primevueTarget);
1206
+ }
1207
+
1208
+ // src/targets/mui.js
1209
+ var SORB_MUI_VARS_FORMAT_ID = "sorb/mui-vars";
1210
+ var muiTarget = {
1211
+ id: "mui",
1212
+ emitFormat: SORB_MUI_VARS_FORMAT_ID,
1213
+ // Kit-vocab expectPrefixes (field-correction, non-negotiable): the
1214
+ // payload-side kit namespace, NEVER the framework's own `mui-` var
1215
+ // prefix — a framework-prefix guard false-positives on every working
1216
+ // preview (verified P2/P3 across the demo program). Overridable via
1217
+ // `config.preview.expectPrefixes`.
1218
+ expectPrefixes: ["color-", "radius-"],
1219
+ // Left undefined on purpose — see file header.
1220
+ inject: void 0,
1221
+ // CONVENTION-DECLARED, NOT demo-verified — the MUI JJ demo never built
1222
+ // dark mode (acid-wash is a variant push, not a mode). MUI v6's documented
1223
+ // `cssVariables: { colorSchemeSelector: 'data' }` convention sets a
1224
+ // `data-mui-color-scheme` attribute (`InitColorSchemeScript` / MUI's
1225
+ // `ThemeProvider` manage it). Feeds the real-dark-mode program's D1 phase;
1226
+ // verification lands there, not here.
1227
+ darkMode: {
1228
+ strategy: "attribute",
1229
+ attribute: "data-mui-color-scheme",
1230
+ darkSelector: '[data-mui-color-scheme="dark"]',
1231
+ lightSelector: '[data-mui-color-scheme="light"]'
1232
+ }
1233
+ };
1234
+ if (typeof registerTarget === "function") {
1235
+ registerTarget(muiTarget);
1236
+ }
1237
+
1238
+ // src/targets/angularMaterial.js
1239
+ var SORB_MAT_SYS_VARS_FORMAT_ID = "sorb/mat-sys-vars";
1240
+ var angularMaterialTarget = {
1241
+ id: "angular-material",
1242
+ emitFormat: SORB_MAT_SYS_VARS_FORMAT_ID,
1243
+ // Kit-vocab expectPrefixes (field-correction, non-negotiable): the
1244
+ // payload-side kit namespace, NEVER Angular Material's own `mat-sys-`
1245
+ // var prefix — a framework-prefix guard false-positives on every working
1246
+ // preview (verified P2/P3 across the demo program). Overridable via
1247
+ // `config.preview.expectPrefixes`.
1248
+ expectPrefixes: ["color-", "radius-"],
1249
+ // Left undefined on purpose — see file header. Non-React (Angular) target;
1250
+ // the leaf-core inject seam stays deferred.
1251
+ inject: void 0,
1252
+ // CONVENTION-DECLARED + UNCONFIRMED — see file header "DARK MODE" section.
1253
+ // Best-known default given Angular Material 20's `light-dark()`/
1254
+ // `color-scheme` mechanism has no single canonical selector name.
1255
+ darkMode: {
1256
+ strategy: "class",
1257
+ darkSelector: ".dark"
1258
+ }
1259
+ };
1260
+ if (typeof registerTarget === "function") {
1261
+ registerTarget(angularMaterialTarget);
1262
+ }
365
1263
  export {
1264
+ MODE_STYLESHEET_ID,
366
1265
  PreviewBanner,
367
1266
  SorbProvider,
1267
+ ThemeToggle,
1268
+ angularMaterialTarget,
1269
+ applyLegacyMap,
1270
+ buildModeStylesheet,
1271
+ clearLegacyMap,
1272
+ clearModeStylesheet,
1273
+ computeLegacyOverride,
1274
+ dataThemeDarkMode,
1275
+ indexLegacyMap,
1276
+ injectModeStylesheet,
1277
+ mantineTarget,
1278
+ muiTarget,
1279
+ normalizeProp,
1280
+ normalizeValue,
1281
+ primevueTarget,
1282
+ reactBootstrapTarget,
368
1283
  sanitizeCssValue,
1284
+ shadcnTarget,
1285
+ sorbInit,
1286
+ tailwindDarkMode,
1287
+ tailwindV4Target,
369
1288
  useIsPreview,
370
1289
  usePreviewState,
1290
+ useTheme,
371
1291
  useToken,
372
1292
  useTokens,
373
1293
  verifyResolved