@pure-ds/storybook 0.1.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.
Files changed (129) hide show
  1. package/.storybook/addons/description/preview.js +15 -0
  2. package/.storybook/addons/description/register.js +60 -0
  3. package/.storybook/addons/html-preview/Panel.jsx +327 -0
  4. package/.storybook/addons/html-preview/constants.js +6 -0
  5. package/.storybook/addons/html-preview/preview.js +178 -0
  6. package/.storybook/addons/html-preview/register.js +16 -0
  7. package/.storybook/addons/pds-configurator/SearchTool.js +44 -0
  8. package/.storybook/addons/pds-configurator/Tool.js +30 -0
  9. package/.storybook/addons/pds-configurator/constants.js +9 -0
  10. package/.storybook/addons/pds-configurator/preview.js +159 -0
  11. package/.storybook/addons/pds-configurator/register.js +24 -0
  12. package/.storybook/docs.css +35 -0
  13. package/.storybook/htmlPreview.css +103 -0
  14. package/.storybook/htmlPreview.js +271 -0
  15. package/.storybook/main.js +160 -0
  16. package/.storybook/preview-body.html +48 -0
  17. package/.storybook/preview-head.html +11 -0
  18. package/.storybook/preview.js +1563 -0
  19. package/README.md +266 -0
  20. package/bin/index.js +40 -0
  21. package/dist/pds-reference.json +2101 -0
  22. package/package.json +45 -0
  23. package/pds.config.js +6 -0
  24. package/public/assets/css/app.css +1216 -0
  25. package/public/assets/data/auto-design-advanced.json +704 -0
  26. package/public/assets/data/auto-design-simple.json +123 -0
  27. package/public/assets/img/icon-512x512.png +0 -0
  28. package/public/assets/img/logo-trans.png +0 -0
  29. package/public/assets/img/logo.png +0 -0
  30. package/public/assets/js/app.js +15088 -0
  31. package/public/assets/js/app.js.map +7 -0
  32. package/public/assets/js/lit.js +1176 -0
  33. package/public/assets/js/lit.js.map +7 -0
  34. package/public/assets/js/pds.js +9801 -0
  35. package/public/assets/js/pds.js.map +7 -0
  36. package/public/assets/pds/components/pds-calendar.js +837 -0
  37. package/public/assets/pds/components/pds-drawer.js +857 -0
  38. package/public/assets/pds/components/pds-icon.js +338 -0
  39. package/public/assets/pds/components/pds-jsonform.js +1775 -0
  40. package/public/assets/pds/components/pds-richtext.js +1035 -0
  41. package/public/assets/pds/components/pds-scrollrow.js +331 -0
  42. package/public/assets/pds/components/pds-splitpanel.js +401 -0
  43. package/public/assets/pds/components/pds-tabstrip.js +251 -0
  44. package/public/assets/pds/components/pds-toaster.js +446 -0
  45. package/public/assets/pds/components/pds-upload.js +657 -0
  46. package/public/assets/pds/custom-elements.json +2003 -0
  47. package/public/assets/pds/icons/pds-icons.svg +498 -0
  48. package/public/assets/pds/pds-css-complete.json +1861 -0
  49. package/public/assets/pds/pds-runtime-config.json +11 -0
  50. package/public/assets/pds/pds.css-data.json +2152 -0
  51. package/public/assets/pds/styles/pds-components.css +1944 -0
  52. package/public/assets/pds/styles/pds-components.css.js +3895 -0
  53. package/public/assets/pds/styles/pds-primitives.css +352 -0
  54. package/public/assets/pds/styles/pds-primitives.css.js +711 -0
  55. package/public/assets/pds/styles/pds-styles.css +3761 -0
  56. package/public/assets/pds/styles/pds-styles.css.js +7529 -0
  57. package/public/assets/pds/styles/pds-tokens.css +699 -0
  58. package/public/assets/pds/styles/pds-tokens.css.js +1405 -0
  59. package/public/assets/pds/styles/pds-utilities.css +763 -0
  60. package/public/assets/pds/styles/pds-utilities.css.js +1533 -0
  61. package/public/assets/pds/vscode-custom-data.json +824 -0
  62. package/scripts/build-pds-reference.mjs +807 -0
  63. package/scripts/generate-stories.js +542 -0
  64. package/scripts/package-build.js +86 -0
  65. package/src/js/app.js +17 -0
  66. package/src/js/common/ask.js +208 -0
  67. package/src/js/common/common.js +20 -0
  68. package/src/js/common/font-loader.js +200 -0
  69. package/src/js/common/msg.js +90 -0
  70. package/src/js/lit.js +40 -0
  71. package/src/js/pds-core/pds-config.js +1162 -0
  72. package/src/js/pds-core/pds-enhancer-metadata.js +75 -0
  73. package/src/js/pds-core/pds-enhancers.js +357 -0
  74. package/src/js/pds-core/pds-enums.js +86 -0
  75. package/src/js/pds-core/pds-generator.js +5317 -0
  76. package/src/js/pds-core/pds-ontology.js +256 -0
  77. package/src/js/pds-core/pds-paths.js +109 -0
  78. package/src/js/pds-core/pds-query.js +571 -0
  79. package/src/js/pds-core/pds-registry.js +129 -0
  80. package/src/js/pds-core/pds.d.ts +129 -0
  81. package/src/js/pds.d.ts +408 -0
  82. package/src/js/pds.js +1579 -0
  83. package/src/pds-core/pds-api.js +105 -0
  84. package/stories/GettingStarted.md +96 -0
  85. package/stories/GettingStarted.stories.js +144 -0
  86. package/stories/WhatIsPDS.md +194 -0
  87. package/stories/WhatIsPDS.stories.js +144 -0
  88. package/stories/components/PdsCalendar.stories.js +263 -0
  89. package/stories/components/PdsDrawer.stories.js +623 -0
  90. package/stories/components/PdsIcon.stories.js +78 -0
  91. package/stories/components/PdsJsonform.stories.js +1444 -0
  92. package/stories/components/PdsRichtext.stories.js +367 -0
  93. package/stories/components/PdsScrollrow.stories.js +140 -0
  94. package/stories/components/PdsSplitpanel.stories.js +502 -0
  95. package/stories/components/PdsTabstrip.stories.js +442 -0
  96. package/stories/components/PdsToaster.stories.js +186 -0
  97. package/stories/components/PdsUpload.stories.js +66 -0
  98. package/stories/enhancements/Dropdowns.stories.js +185 -0
  99. package/stories/enhancements/InteractiveStates.stories.js +625 -0
  100. package/stories/enhancements/MeshGradients.stories.js +320 -0
  101. package/stories/enhancements/OpenGroups.stories.js +227 -0
  102. package/stories/enhancements/RangeSliders.stories.js +232 -0
  103. package/stories/enhancements/RequiredFields.stories.js +189 -0
  104. package/stories/enhancements/Toggles.stories.js +167 -0
  105. package/stories/foundations/Colors.stories.js +283 -0
  106. package/stories/foundations/Icons.stories.js +305 -0
  107. package/stories/foundations/SmartSurfaces.stories.js +367 -0
  108. package/stories/foundations/Spacing.stories.js +175 -0
  109. package/stories/foundations/Typography.stories.js +960 -0
  110. package/stories/foundations/ZIndex.stories.js +325 -0
  111. package/stories/patterns/BorderEffects.stories.js +72 -0
  112. package/stories/patterns/Layout.stories.js +99 -0
  113. package/stories/patterns/Utilities.stories.js +107 -0
  114. package/stories/primitives/Accordion.stories.js +359 -0
  115. package/stories/primitives/Alerts.stories.js +64 -0
  116. package/stories/primitives/Badges.stories.js +183 -0
  117. package/stories/primitives/Buttons.stories.js +229 -0
  118. package/stories/primitives/Cards.stories.js +353 -0
  119. package/stories/primitives/FormGroups.stories.js +569 -0
  120. package/stories/primitives/Forms.stories.js +131 -0
  121. package/stories/primitives/Media.stories.js +203 -0
  122. package/stories/primitives/Tables.stories.js +232 -0
  123. package/stories/reference/ReferenceCatalog.stories.js +28 -0
  124. package/stories/reference/reference-catalog.js +413 -0
  125. package/stories/reference/reference-docs.js +302 -0
  126. package/stories/reference/reference-helpers.js +310 -0
  127. package/stories/utilities/GridSystem.stories.js +208 -0
  128. package/stories/utils/PdsAsk.stories.js +420 -0
  129. package/stories/utils/toast-utils.js +148 -0
package/src/js/pds.js ADDED
@@ -0,0 +1,1579 @@
1
+ /// <reference path="./pds.d.ts" />
2
+
3
+ /**
4
+ * Public PDS runtime object exported to consumers.
5
+ *
6
+ * This object exposes the core runtime building blocks for the Pure Design System.
7
+ * It intentionally provides a small, stable surface area so consuming apps can:
8
+ * - programmatically generate design system artifacts (via `Generator`),
9
+ * - adopt styles into Shadow DOM (via `adoptLayers` / `adoptPrimitives`),
10
+ * - query runtime mode and obtain constructable stylesheets (via `registry`).
11
+ *
12
+ * Common events in the PDS ecosystem (emitted by other packages/components):
13
+ * - `design-updated` — emitted by the designer component when the in-memory design changes (detail: { config }).
14
+ * - `pds-generated` — emitted by the PDS configurator when generation completes (detail: { modules, meta }).
15
+ * - `pds-error` — emitted by the PDS configurator when generation fails (detail: Error).
16
+ *
17
+ * Error handling notes:
18
+ * - Methods that perform dynamic imports (e.g. `adoptLayers` via the registry) may log and return fallbacks
19
+ * rather than throwing; consumers should check return values (e.g. null) and listen for `pds-error` in UI flows.
20
+ * - `createStylesheet(css)` will throw synchronously if the provided CSS cannot be parsed by the browser's
21
+ * `CSSStyleSheet.replaceSync()` — callers should guard invalid input or wrap calls in try/catch.
22
+ *
23
+ * @typedef {Object} PDSAPI
24
+ * @property {typeof import("./pds-core/pds-generator.js").Generator} Generator - Generator class to produce design system assets
25
+ * @property {import("./pds-core/pds-registry.js").PDSRegistry} registry - Singleton runtime registry for live/static mode
26
+ * @property {any} ontology - Ontology helpers and metadata for components
27
+ * @property {(shadowRoot: ShadowRoot, layers?: string[], additionalSheets?: CSSStyleSheet[]) => Promise<void>} adoptLayers - Adopt multiple layers into a ShadowRoot. May log errors and fallback to additionalSheets when static imports fail.
28
+ * @property {(shadowRoot: ShadowRoot, additionalSheets?: CSSStyleSheet[]) => Promise<void>} adoptPrimitives - Adopt primitives layer into a ShadowRoot. Designed as a convenience for components.
29
+ * @property {(css:string) => CSSStyleSheet} createStylesheet - Create a constructable stylesheet from CSS text. @throws {DOMException} on invalid CSS in some browsers.
30
+ * @property {() => boolean} isLiveMode - Returns true when running in live/designer-backed mode
31
+ * @property {(el: Element) => import("./pds-core/pds-ontology.js").ComponentDef | null} findComponentForElement - Helper to find a component definition for a DOM element
32
+ */
33
+
34
+ /**
35
+ * Workspace for the Pure Design System runtime API
36
+ * PDS is now an EventTarget so consumers can subscribe to a single, consistent
37
+ * event bus instead of listening on window/document or individual elements.
38
+ */
39
+ class PDSBase extends EventTarget {}
40
+ /** @type {PDSAPI & PDSBase} */
41
+ const PDS = new PDSBase();
42
+
43
+ import {
44
+ Generator,
45
+ adoptLayers,
46
+ adoptPrimitives,
47
+ createStylesheet,
48
+ isLiveMode,
49
+ } from "./pds-core/pds-generator.js";
50
+ import { registry } from "./pds-core/pds-registry.js";
51
+ import ontology from "./pds-core/pds-ontology.js";
52
+ import { findComponentForElement } from "./pds-core/pds-ontology.js";
53
+ import { presets, defaultLog } from "./pds-core/pds-config.js";
54
+ import { enums } from "./pds-core/pds-enums.js";
55
+ import { ask } from "./common/ask.js";
56
+ import { PDSQuery } from "./pds-core/pds-query.js";
57
+ import * as common from "./common/common.js";
58
+ import { defaultPDSEnhancers } from "./pds-core/pds-enhancers.js";
59
+ import { resolvePublicAssetURL } from "./pds-core/pds-paths.js";
60
+
61
+ // Font loading utilities
62
+ import { loadTypographyFonts } from "./common/font-loader.js";
63
+
64
+ /** Generator class — use to programmatically create design system assets from a config */
65
+ PDS.Generator = Generator;
66
+
67
+ /** Singleton runtime registry. Use `registry.setDesigner()` to enable live mode or `registry.setStaticMode()` for static assets */
68
+ PDS.registry = registry;
69
+
70
+ /** Ontology and metadata about components and tokens */
71
+ PDS.ontology = ontology;
72
+
73
+ /** Adopt a set of layered stylesheets into a ShadowRoot */
74
+ PDS.adoptLayers = adoptLayers;
75
+
76
+ /** Convenience to adopt only primitives into a ShadowRoot */
77
+ PDS.adoptPrimitives = adoptPrimitives;
78
+
79
+ /** Create a constructable CSSStyleSheet from a CSS string */
80
+ PDS.createStylesheet = createStylesheet;
81
+
82
+ /** Return true when running inside a live/designer-backed environment */
83
+ PDS.isLiveMode = isLiveMode;
84
+
85
+ PDS.enums = enums;
86
+
87
+ PDS.ask = ask;
88
+
89
+ // Expose common utilities (deepMerge, isObject, etc.)
90
+ PDS.common = common;
91
+
92
+ // Expose presets object directly
93
+ PDS.presets = presets;
94
+
95
+ /** Find a component definition (ontology) for a given DOM element */
96
+ PDS.findComponentForElement = findComponentForElement;
97
+
98
+ /**
99
+ * Smart query interface for design system questions
100
+ * @param {string} question - Natural language query about tokens, components, utilities, or patterns
101
+ * @returns {Promise<Array>} Array of results with text, value, icon, category, code examples
102
+ * @example
103
+ * const results = await PDS.query("what is the focus border color on inputs?");
104
+ * const results = await PDS.query("how do I create an icon-only button?");
105
+ */
106
+ PDS.query = async function(question) {
107
+ const queryEngine = new PDSQuery(PDS);
108
+ return await queryEngine.search(question);
109
+ };
110
+
111
+ function __emitPDSReady(detail) {
112
+ const hasCustomEvent = typeof CustomEvent === "function";
113
+
114
+ try {
115
+ const readyEvent = hasCustomEvent
116
+ ? new CustomEvent("pds:ready", { detail })
117
+ : new Event("pds:ready");
118
+ PDS.dispatchEvent(readyEvent);
119
+ } catch (e) {}
120
+
121
+ if (typeof document !== "undefined") {
122
+ if (hasCustomEvent) {
123
+ const eventOptions = { detail, bubbles: true, composed: true };
124
+ try {
125
+ document.dispatchEvent(new CustomEvent("pds:ready", eventOptions));
126
+ } catch (e) {}
127
+ try {
128
+ document.dispatchEvent(new CustomEvent("pds-ready", eventOptions));
129
+ } catch (e) {}
130
+ } else {
131
+ try {
132
+ document.dispatchEvent(new Event("pds:ready"));
133
+ } catch (e) {}
134
+ try {
135
+ document.dispatchEvent(new Event("pds-ready"));
136
+ } catch (e) {}
137
+ }
138
+ }
139
+ }
140
+
141
+ /** Current configuration (set after PDS.start() completes) - read-only, frozen after initialization */
142
+ Object.defineProperty(PDS, "currentConfig", {
143
+ value: null,
144
+ writable: true,
145
+ enumerable: true,
146
+ configurable: false,
147
+ });
148
+
149
+ /**
150
+ * Compiled design system state - provides structured access to all generated tokens,
151
+ * layers, and metadata. Available in live mode when a generator is active.
152
+ * Returns the generator's compiled representation or null if not in live mode.
153
+ *
154
+ * Structure includes:
155
+ * - tokens: All generated token groups (colors, spacing, typography, etc.)
156
+ * - layers: CSS content and metadata for each layer (tokens, primitives, components, utilities)
157
+ * - config: Configuration snapshot used to generate the current state
158
+ * - capabilities: Runtime environment capabilities
159
+ * - references: Links to ontology and enums for introspection
160
+ * - meta: Computed metadata about the design system
161
+ * - helpers: Utility methods to query the compiled state
162
+ */
163
+ Object.defineProperty(PDS, "compiled", {
164
+ get() {
165
+ // Only available in live mode when we have a generator
166
+ if (PDS.registry?.isLive && PDS.registry?._designer) {
167
+ return PDS.registry._designer.compiled;
168
+ }
169
+ return null;
170
+ },
171
+ enumerable: true,
172
+ configurable: false,
173
+ });
174
+
175
+ // Always expose PDS on the window in browser contexts so consumers can access it in both live and static modes
176
+ if (typeof window !== "undefined") {
177
+ // @ts-ignore
178
+ window.PDS = PDS;
179
+ }
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // FOUC Prevention: Add pds-ready class when PDS is fully initialized in live mode
183
+ // This works in conjunction with CSS injected by the live() function
184
+ if (typeof document !== "undefined") {
185
+ PDS.addEventListener("pds:ready", (event) => {
186
+ const mode = event.detail?.mode;
187
+ if (mode) {
188
+ // Add mode-specific class (pds-live or pds-static)
189
+ document.documentElement.classList.add(`pds-${mode}`);
190
+
191
+ // Only add pds-ready class in live mode for FOUC prevention
192
+ if (mode === "live") {
193
+ document.documentElement.classList.add("pds-ready");
194
+ }
195
+ }
196
+ });
197
+ }
198
+
199
+ // ---------------------------------------------------------------------------
200
+ // Theme management (centralized on PDS.theme)
201
+ // Consumers may read/write `PDS.theme` with values: 'system' | 'light' | 'dark'
202
+ // Setting the property persists to localStorage (when available), updates
203
+ // document.documentElement[data-theme] to an explicit 'light'|'dark' value
204
+ // and emits a `pds:theme:changed` event. Reading the property returns the
205
+ // raw stored preference (or null if none).
206
+
207
+ // Private module-level variables for theme management
208
+ const __themeStorageKey = "pure-ds-theme";
209
+ let __themeMQ = null;
210
+ let __themeMQListener = null;
211
+
212
+ // Private: Apply resolved theme to document
213
+ function __applyResolvedTheme(raw) {
214
+ try {
215
+ if (typeof document === "undefined") return;
216
+ let resolved = "light";
217
+ if (!raw) {
218
+ // No stored preference: use OS preference
219
+ const prefersDark =
220
+ typeof window !== "undefined" &&
221
+ window.matchMedia &&
222
+ window.matchMedia("(prefers-color-scheme: dark)").matches;
223
+ resolved = prefersDark ? "dark" : "light";
224
+ } else if (raw === "system") {
225
+ const prefersDark =
226
+ typeof window !== "undefined" &&
227
+ window.matchMedia &&
228
+ window.matchMedia("(prefers-color-scheme: dark)").matches;
229
+ resolved = prefersDark ? "dark" : "light";
230
+ } else {
231
+ resolved = raw;
232
+ }
233
+ document.documentElement.setAttribute("data-theme", resolved);
234
+ } catch (e) {}
235
+ }
236
+
237
+ // Private: Setup system theme change listener when needed
238
+ function __setupSystemListenerIfNeeded(raw) {
239
+ try {
240
+ // Remove any existing listener first
241
+ if (__themeMQ && __themeMQListener) {
242
+ try {
243
+ if (typeof __themeMQ.removeEventListener === "function")
244
+ __themeMQ.removeEventListener("change", __themeMQListener);
245
+ else if (typeof __themeMQ.removeListener === "function")
246
+ __themeMQ.removeListener(__themeMQListener);
247
+ } catch (e) {}
248
+ __themeMQ = null;
249
+ __themeMQListener = null;
250
+ }
251
+
252
+ if (
253
+ raw === "system" &&
254
+ typeof window !== "undefined" &&
255
+ window.matchMedia
256
+ ) {
257
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
258
+ const listener = (e) => {
259
+ const isDark = e?.matches === undefined ? mq.matches : e.matches;
260
+ try {
261
+ const newTheme = isDark ? "dark" : "light";
262
+ document.documentElement.setAttribute("data-theme", newTheme);
263
+ PDS.dispatchEvent(
264
+ new CustomEvent("pds:theme:changed", {
265
+ detail: { theme: newTheme, source: "system" },
266
+ })
267
+ );
268
+ } catch (ex) {}
269
+ };
270
+ __themeMQ = mq;
271
+ __themeMQListener = listener;
272
+ if (typeof mq.addEventListener === "function")
273
+ mq.addEventListener("change", listener);
274
+ else if (typeof mq.addListener === "function") mq.addListener(listener);
275
+ }
276
+ } catch (e) {}
277
+ }
278
+
279
+ Object.defineProperty(PDS, "theme", {
280
+ get() {
281
+ try {
282
+ if (typeof window === "undefined") return null;
283
+ return localStorage.getItem(__themeStorageKey) || null;
284
+ } catch (e) {
285
+ return null;
286
+ }
287
+ },
288
+ set(value) {
289
+ try {
290
+ if (typeof window === "undefined") return;
291
+ if (value === null || value === undefined) {
292
+ localStorage.removeItem(__themeStorageKey);
293
+ } else {
294
+ localStorage.setItem(__themeStorageKey, value);
295
+ }
296
+
297
+ // Apply resolved (light/dark) value to document
298
+ __applyResolvedTheme(value);
299
+ // Setup system change listener only when 'system' is selected
300
+ __setupSystemListenerIfNeeded(value);
301
+
302
+ // Emit a notification with the raw preference (value may be 'system')
303
+ PDS.dispatchEvent(
304
+ new CustomEvent("pds:theme:changed", {
305
+ detail: { theme: value, source: "api" },
306
+ })
307
+ );
308
+ } catch (e) {}
309
+ },
310
+ });
311
+
312
+ // ----------------------------------------------------------------------------
313
+ // Default Enhancers — first-class citizens alongside AutoDefiner
314
+ // ----------------------------------------------------------------------------
315
+ /**
316
+ * Default DOM enhancers shipped with PDS. These are lightweight progressive
317
+ * enhancements that can be applied to vanilla markup. Consumers can override
318
+ * or add to these via the `enhancers` option of PDS.start({ mode }).
319
+ */
320
+ PDS.defaultEnhancers = defaultPDSEnhancers;
321
+
322
+ /**
323
+ * Validate a design configuration for accessibility sanity checks.
324
+ * Currently validates color contrast for primary buttons and base surface text
325
+ * in both light and dark themes.
326
+ *
327
+ * @param {object} designConfig - A full or partial PDS config object
328
+ * @param {object} [options]
329
+ * @param {number} [options.minContrast=4.5] - Minimum contrast ratio for normal text
330
+ * @returns {{ ok: boolean, issues: Array<{path:string, message:string, ratio:number, min:number, context?:string}> }}
331
+ */
332
+ function validateDesign(designConfig = {}, options = {}) {
333
+ const MIN = Number(options.minContrast || 4.5);
334
+
335
+ // Local helpers (keep public; no dependency on private Generator methods)
336
+ const hexToRgb = (hex) => {
337
+ const h = String(hex || "").replace("#", "");
338
+ const full =
339
+ h.length === 3
340
+ ? h
341
+ .split("")
342
+ .map((c) => c + c)
343
+ .join("")
344
+ : h;
345
+ const num = parseInt(full || "0", 16);
346
+ return { r: (num >> 16) & 255, g: (num >> 8) & 255, b: num & 255 };
347
+ };
348
+ const luminance = (hex) => {
349
+ const { r, g, b } = hexToRgb(hex);
350
+ const srgb = [r / 255, g / 255, b / 255].map((v) =>
351
+ v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)
352
+ );
353
+ return 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2];
354
+ };
355
+ const contrast = (a, b) => {
356
+ if (!a || !b) return 0;
357
+ const L1 = luminance(a);
358
+ const L2 = luminance(b);
359
+ const lighter = Math.max(L1, L2);
360
+ const darker = Math.min(L1, L2);
361
+ return (lighter + 0.05) / (darker + 0.05);
362
+ };
363
+
364
+ const issues = [];
365
+ try {
366
+ // Build tokens from the candidate config
367
+ const gen = new PDS.Generator({ design: structuredClone(designConfig) });
368
+ const c = gen.tokens.colors;
369
+
370
+ // Light theme checks - use computed interactive tokens
371
+ const light = {
372
+ surfaceBg: c.surface?.base,
373
+ surfaceText: c.gray?.[900] || "#000000",
374
+ primaryFill: c.interactive?.light?.fill || c.primary?.[600],
375
+ primaryText: c.interactive?.light?.text || c.primary?.[600],
376
+ };
377
+
378
+ // Primary button (light): check button fill with white text
379
+ const lightBtnRatio = contrast(light.primaryFill, "#ffffff");
380
+ if (lightBtnRatio < MIN) {
381
+ issues.push({
382
+ path: "/colors/primary",
383
+ message: `Primary button contrast too low in light theme (${lightBtnRatio.toFixed(
384
+ 2
385
+ )} < ${MIN}). Choose a darker primary.`,
386
+ ratio: lightBtnRatio,
387
+ min: MIN,
388
+ context: "light/btn-primary",
389
+ });
390
+ }
391
+
392
+ // Surface text (light): text vs surface base
393
+ const lightTextRatio = contrast(light.surfaceBg, light.surfaceText);
394
+ if (lightTextRatio < MIN) {
395
+ issues.push({
396
+ path: "/colors/background",
397
+ message: `Base text contrast on surface (light) is too low (${lightTextRatio.toFixed(
398
+ 2
399
+ )} < ${MIN}). Adjust background or secondary (gray).`,
400
+ ratio: lightTextRatio,
401
+ min: MIN,
402
+ context: "light/surface-text",
403
+ });
404
+ }
405
+
406
+ // Primary text for outline/link: check link text on surface
407
+ const lightOutlineRatio = contrast(light.primaryText, light.surfaceBg);
408
+ if (lightOutlineRatio < MIN) {
409
+ issues.push({
410
+ path: "/colors/primary",
411
+ message: `Primary text on surface is too low for outline/link styles (light) (${lightOutlineRatio.toFixed(
412
+ 2
413
+ )} < ${MIN}). Choose a darker primary or lighter surface.`,
414
+ ratio: lightOutlineRatio,
415
+ min: MIN,
416
+ context: "light/outline",
417
+ });
418
+ }
419
+
420
+ // Dark theme checks - use computed interactive tokens
421
+ const d = c.dark;
422
+ if (d) {
423
+ const dark = {
424
+ surfaceBg: d.surface?.base || c.surface?.inverse,
425
+ primaryFill: c.interactive?.dark?.fill || d.primary?.[600],
426
+ primaryText: c.interactive?.dark?.text || d.primary?.[600],
427
+ };
428
+
429
+ // Primary button (dark): check button fill with white text
430
+ const darkBtnRatio = contrast(dark.primaryFill, "#ffffff");
431
+ if (darkBtnRatio < MIN) {
432
+ issues.push({
433
+ path: "/colors/darkMode/primary",
434
+ message: `Primary button contrast too low in dark theme (${darkBtnRatio.toFixed(
435
+ 2
436
+ )} < ${MIN}). Override darkMode.primary or pick a brighter hue.`,
437
+ ratio: darkBtnRatio,
438
+ min: MIN,
439
+ context: "dark/btn-primary",
440
+ });
441
+ }
442
+
443
+ // Outline/link style in dark: check link text on dark surface
444
+ const darkOutlineRatio = contrast(dark.primaryText, dark.surfaceBg);
445
+ if (darkOutlineRatio < MIN) {
446
+ issues.push({
447
+ path: "/colors/darkMode/primary",
448
+ message: `Primary text on surface is too low for outline/link styles (dark) (${darkOutlineRatio.toFixed(
449
+ 2
450
+ )} < ${MIN}). Override darkMode.primary/background.`,
451
+ ratio: darkOutlineRatio,
452
+ min: MIN,
453
+ context: "dark/outline",
454
+ });
455
+ }
456
+ }
457
+ } catch (err) {
458
+ issues.push({
459
+ path: "/",
460
+ message: `Validation failed: ${String(err?.message || err)}`,
461
+ ratio: 0,
462
+ min: 0,
463
+ });
464
+ }
465
+
466
+ return { ok: issues.length === 0, issues };
467
+ }
468
+
469
+ /** Expose validator on the public API */
470
+ PDS.validateDesign = validateDesign;
471
+
472
+ /**
473
+ * Validate multiple design configurations at once.
474
+ * Useful for build-time enforcement of preset compliance.
475
+ *
476
+ * @param {Array<object>} designs - Array of design configs; items may include an optional `name` property.
477
+ * @param {object} [options] - Options forwarded to validateDesign (e.g., { minContrast })
478
+ * @returns {{ ok: boolean, results: Array<{ name?: string, ok: boolean, issues: Array<{path:string, message:string, ratio:number, min:number, context?:string}> }> }}
479
+ */
480
+ function validateDesigns(designs = [], options = {}) {
481
+ const results = [];
482
+
483
+ const list = Array.isArray(designs)
484
+ ? designs
485
+ : designs && typeof designs === "object"
486
+ ? Object.values(designs)
487
+ : [];
488
+
489
+ for (const item of list) {
490
+ let name;
491
+ let configToValidate = null;
492
+
493
+ // Accept a few shapes:
494
+ // - string => treat as preset id/name
495
+ // - { preset, design?, name? } => resolve preset then merge overrides
496
+ // - full config object (legacy) => validate directly
497
+ if (typeof item === "string") {
498
+ const id = String(item).toLowerCase();
499
+ const found =
500
+ presets?.[id] ||
501
+ Object.values(presets || {}).find(
502
+ (p) =>
503
+ __slugify(p.name) === id ||
504
+ String(p.name || "").toLowerCase() === id
505
+ );
506
+ if (!found) {
507
+ results.push({
508
+ name: item,
509
+ ok: false,
510
+ issues: [
511
+ {
512
+ path: "/",
513
+ message: `Preset not found: ${item}`,
514
+ ratio: 0,
515
+ min: 0,
516
+ },
517
+ ],
518
+ });
519
+ continue;
520
+ }
521
+ name = found.name || id;
522
+ configToValidate = structuredClone(found);
523
+ } else if (item && typeof item === "object") {
524
+ name = item.name || item.preset || undefined;
525
+ if ("preset" in item || "design" in item) {
526
+ const effectivePreset = String(item.preset || "default").toLowerCase();
527
+ const found =
528
+ presets?.[effectivePreset] ||
529
+ Object.values(presets || {}).find(
530
+ (p) =>
531
+ __slugify(p.name) === effectivePreset ||
532
+ String(p.name || "").toLowerCase() === effectivePreset
533
+ );
534
+ if (!found) {
535
+ results.push({
536
+ name,
537
+ ok: false,
538
+ issues: [
539
+ {
540
+ path: "/",
541
+ message: `Preset not found: ${item.preset}`,
542
+ ratio: 0,
543
+ min: 0,
544
+ },
545
+ ],
546
+ });
547
+ continue;
548
+ }
549
+ let base = structuredClone(found);
550
+ if (item.design && typeof item.design === "object") {
551
+ base = __deepMerge(base, structuredClone(item.design));
552
+ }
553
+ configToValidate = base;
554
+ } else {
555
+ // Assume a full config object
556
+ configToValidate = item;
557
+ }
558
+ }
559
+
560
+ if (!configToValidate) {
561
+ results.push({
562
+ name,
563
+ ok: false,
564
+ issues: [
565
+ { path: "/", message: "Invalid design entry", ratio: 0, min: 0 },
566
+ ],
567
+ });
568
+ continue;
569
+ }
570
+
571
+ const { ok, issues } = validateDesign(configToValidate, options);
572
+ results.push({ name, ok, issues });
573
+ }
574
+
575
+ return { ok: results.every((r) => r.ok), results };
576
+ }
577
+
578
+ /** Expose batch validator on the public API */
579
+ PDS.validateDesigns = validateDesigns;
580
+
581
+ /**
582
+ * Initialize PDS in live mode with the given configuration (new unified shape).
583
+ * Typically invoked via PDS.start({ mode: 'live', ... }).
584
+ *
585
+ * Shape:
586
+ * PDS.start({
587
+ * mode: 'live',
588
+ * preset?: string,
589
+ * design?: object,
590
+ * autoDefine?: {
591
+ * baseURL?: string,
592
+ * predefine?: string[],
593
+ * mapper?: (tag:string)=>string|undefined|null, // return undefined/null/false to let PDS default mapper handle it
594
+ * // plus any AutoDefiner flags: scanExisting, observeShadows, patchAttachShadow, debounceMs, onError
595
+ * },
596
+ * // runtime flags (optional)
597
+ * applyGlobalStyles?: boolean,
598
+ * manageTheme?: boolean,
599
+ * themeStorageKey?: string,
600
+ * preloadStyles?: boolean,
601
+ * criticalLayers?: string[]
602
+ * })
603
+ *
604
+ * @param {object} config - The PDS configuration object (unified shape)
605
+ * @returns {Promise<{generator: Generator, config: object, theme: string, autoDefiner?: any}>}
606
+ *
607
+ * @example
608
+ * await PDS.start({ mode: 'live',
609
+ * preset: 'paper-and-ink',
610
+ * design: { colors: { accent: '#FF4081' } },
611
+ * autoDefine: { predefine: ['pds-icon'] }
612
+ * });
613
+ */
614
+ // Internal: resolve theme and set html[data-theme], return resolvedTheme and storedTheme
615
+ function __resolveThemeAndApply({ manageTheme, themeStorageKey }) {
616
+ let resolvedTheme = "light";
617
+ let storedTheme = null;
618
+ if (manageTheme && typeof window !== "undefined") {
619
+ // Read raw preference (may be null, 'system', 'light', 'dark') using provided storage key
620
+ try {
621
+ storedTheme = localStorage.getItem(themeStorageKey) || null;
622
+ } catch (e) {
623
+ storedTheme = null;
624
+ }
625
+
626
+ // Apply the resolved theme and ensure system listener exists when needed
627
+ try {
628
+ __applyResolvedTheme(storedTheme);
629
+ __setupSystemListenerIfNeeded(storedTheme);
630
+ } catch (e) {}
631
+
632
+ // Compute explicit resolvedTheme to return
633
+ if (storedTheme) {
634
+ if (storedTheme === "system") {
635
+ const prefersDark =
636
+ window.matchMedia &&
637
+ window.matchMedia("(prefers-color-scheme: dark)").matches;
638
+ resolvedTheme = prefersDark ? "dark" : "light";
639
+ } else {
640
+ resolvedTheme = storedTheme;
641
+ }
642
+ } else {
643
+ const prefersDark =
644
+ window.matchMedia &&
645
+ window.matchMedia("(prefers-color-scheme: dark)").matches;
646
+ resolvedTheme = prefersDark ? "dark" : "light";
647
+ }
648
+ }
649
+ return { resolvedTheme, storedTheme };
650
+ }
651
+
652
+ // Internal: deep merge utility (arrays replace; objects merge)
653
+ function __deepMerge(target = {}, source = {}) {
654
+ if (!source || typeof source !== "object") return target;
655
+ const out = Array.isArray(target) ? [...target] : { ...target };
656
+ for (const [key, value] of Object.entries(source)) {
657
+ if (value && typeof value === "object" && !Array.isArray(value)) {
658
+ out[key] = __deepMerge(
659
+ out[key] && typeof out[key] === "object" ? out[key] : {},
660
+ value
661
+ );
662
+ } else {
663
+ out[key] = value;
664
+ }
665
+ }
666
+ return out;
667
+ }
668
+
669
+ // Internal: create a slug for matching names like "Paper & Ink" -> "paper-and-ink"
670
+ function __slugify(str = "") {
671
+ return String(str)
672
+ .toLowerCase()
673
+ .replace(/&/g, " and ")
674
+ .replace(/[^a-z0-9]+/g, "-")
675
+ .replace(/^-+|-+$/g, "");
676
+ }
677
+
678
+ // Internal: recursively remove functions from an object to make it cloneable
679
+ function __stripFunctions(obj) {
680
+ if (obj === null || obj === undefined) return obj;
681
+ if (typeof obj === "function") return undefined;
682
+ if (typeof obj !== "object") return obj;
683
+
684
+ if (Array.isArray(obj)) {
685
+ return obj.map(item => __stripFunctions(item)).filter(item => item !== undefined);
686
+ }
687
+
688
+ const result = {};
689
+ for (const key in obj) {
690
+ if (obj.hasOwnProperty(key)) {
691
+ const value = obj[key];
692
+ if (typeof value !== "function") {
693
+ const stripped = __stripFunctions(value);
694
+ if (stripped !== undefined) {
695
+ result[key] = stripped;
696
+ }
697
+ }
698
+ }
699
+ }
700
+ return result;
701
+ }
702
+
703
+ const __ABSOLUTE_URL_PATTERN__ = /^[a-z][a-z0-9+\-.]*:\/\//i;
704
+ const __MODULE_URL__ = (() => {
705
+ try {
706
+ return import.meta.url;
707
+ } catch (e) {
708
+ return undefined;
709
+ }
710
+ })();
711
+
712
+ function __ensureAbsoluteAssetURL(value, options = {}) {
713
+ if (!value || __ABSOLUTE_URL_PATTERN__.test(value)) {
714
+ return value;
715
+ }
716
+
717
+ const { preferModule = true } = options;
718
+
719
+ const tryModule = () => {
720
+ if (!__MODULE_URL__) return null;
721
+ try {
722
+ return new URL(value, __MODULE_URL__).href;
723
+ } catch (e) {
724
+ return null;
725
+ }
726
+ };
727
+
728
+ const tryWindow = () => {
729
+ if (typeof window === "undefined" || !window.location?.origin) {
730
+ return null;
731
+ }
732
+ try {
733
+ return new URL(value, window.location.origin).href;
734
+ } catch (e) {
735
+ return null;
736
+ }
737
+ };
738
+
739
+ const resolved = preferModule
740
+ ? tryModule() || tryWindow()
741
+ : tryWindow() || tryModule();
742
+
743
+ return resolved || value;
744
+ }
745
+
746
+ const __ensureTrailingSlash = (value) =>
747
+ typeof value === "string" && value.length && !value.endsWith("/")
748
+ ? `${value}/`
749
+ : value;
750
+
751
+ const __MODULE_DEFAULT_ASSET_ROOT__ = (() => {
752
+ if (!__MODULE_URL__) return undefined;
753
+ try {
754
+ const parsed = new URL(__MODULE_URL__);
755
+ if (/\/public\/assets\/js\//.test(parsed.pathname)) {
756
+ return new URL("../pds/", __MODULE_URL__).href;
757
+ }
758
+ } catch (e) {
759
+ return undefined;
760
+ }
761
+ return undefined;
762
+ })();
763
+
764
+ function __resolveRuntimeAssetRoot(config) {
765
+ const hasCustomRoot = Boolean(config?.public?.root || config?.static?.root);
766
+ let candidate = resolvePublicAssetURL(config);
767
+
768
+ if (!hasCustomRoot && __MODULE_DEFAULT_ASSET_ROOT__) {
769
+ candidate = __MODULE_DEFAULT_ASSET_ROOT__;
770
+ }
771
+
772
+ return __ensureTrailingSlash(__ensureAbsoluteAssetURL(candidate));
773
+ }
774
+
775
+ // Internal: normalize first-arg config to a full generator config and extract enhancers if provided inline
776
+ function __normalizeInitConfig(inputConfig = {}, options = {}) {
777
+ // If caller passed a plain design config (legacy), keep as-is
778
+ const hasDesignKeys =
779
+ typeof inputConfig === "object" &&
780
+ ("colors" in inputConfig ||
781
+ "typography" in inputConfig ||
782
+ "spatialRhythm" in inputConfig ||
783
+ "shape" in inputConfig ||
784
+ "behavior" in inputConfig ||
785
+ "layout" in inputConfig ||
786
+ "advanced" in inputConfig ||
787
+ "a11y" in inputConfig ||
788
+ "components" in inputConfig ||
789
+ "icons" in inputConfig);
790
+
791
+ // Extract potential inline enhancers from config; prefer inline over options
792
+ let inlineEnhancers = inputConfig && inputConfig.enhancers;
793
+ if (inlineEnhancers && !Array.isArray(inlineEnhancers)) {
794
+ // If an object was provided, convert to array of values
795
+ inlineEnhancers = Object.values(inlineEnhancers);
796
+ }
797
+ const enhancers = inlineEnhancers ?? options.enhancers ?? [];
798
+
799
+ // New API: { preset?: string, design?: object }
800
+ const presetId = inputConfig && inputConfig.preset;
801
+ const designOverrides = inputConfig && inputConfig.design;
802
+
803
+ const hasNewShape =
804
+ "preset" in (inputConfig || {}) ||
805
+ "design" in (inputConfig || {}) ||
806
+ "enhancers" in (inputConfig || {});
807
+
808
+ let generatorConfig;
809
+ let presetInfo = null;
810
+
811
+ if (hasNewShape) {
812
+ // Always resolve a preset; default if none provided
813
+ const effectivePreset = String(presetId || "default").toLowerCase();
814
+ const found =
815
+ presets?.[effectivePreset] ||
816
+ Object.values(presets || {}).find(
817
+ (p) =>
818
+ __slugify(p.name) === effectivePreset ||
819
+ String(p.name || "").toLowerCase() === effectivePreset
820
+ );
821
+ if (!found)
822
+ throw new Error(`PDS preset not found: "${presetId || "default"}"`);
823
+
824
+ presetInfo = {
825
+ id: found.id || __slugify(found.name),
826
+ name: found.name || found.id || String(effectivePreset),
827
+ };
828
+
829
+ // Merge preset with design overrides
830
+ let mergedDesign = structuredClone(found);
831
+ if (designOverrides && typeof designOverrides === "object") {
832
+ // Strip functions before cloning to avoid DataCloneError
833
+ const cloneableDesign = __stripFunctions(designOverrides);
834
+ mergedDesign = __deepMerge(mergedDesign, structuredClone(cloneableDesign));
835
+ }
836
+
837
+ // Build structured config with design nested
838
+ // Exclude runtime-specific properties that shouldn't be passed to Generator
839
+ const {
840
+ mode, autoDefine, applyGlobalStyles, manageTheme,
841
+ themeStorageKey, preloadStyles, criticalLayers,
842
+ preset: _preset, design: _design, enhancers: _enhancers,
843
+ log: userLog, // Extract log if provided at root
844
+ ...otherProps
845
+ } = inputConfig;
846
+
847
+ generatorConfig = {
848
+ ...otherProps, // Keep only generator-relevant properties
849
+ design: mergedDesign,
850
+ preset: presetInfo.name,
851
+ // Add log method at root level (use user's or default)
852
+ log: userLog || defaultLog,
853
+ };
854
+ } else if (hasDesignKeys) {
855
+ // Back-compat: treat the provided object as the full design, wrap it
856
+ // Extract log before cloning to avoid DataCloneError
857
+ const { log: userLog, ...designConfig } = inputConfig;
858
+ generatorConfig = {
859
+ design: structuredClone(designConfig),
860
+ log: userLog || defaultLog,
861
+ };
862
+ } else {
863
+ // Nothing recognizable: use default preset
864
+ const foundDefault =
865
+ presets?.["default"] ||
866
+ Object.values(presets || {}).find((p) => __slugify(p.name) === "default");
867
+ if (!foundDefault) throw new Error("PDS default preset not available");
868
+ presetInfo = {
869
+ id: foundDefault.id || "default",
870
+ name: foundDefault.name || "Default",
871
+ };
872
+ generatorConfig = {
873
+ design: structuredClone(foundDefault),
874
+ preset: presetInfo.name,
875
+ log: defaultLog,
876
+ };
877
+ }
878
+
879
+ return { generatorConfig, enhancers, presetInfo };
880
+ }
881
+
882
+ // Internal: setup AutoDefiner and run enhancers
883
+ async function __setupAutoDefinerAndEnhancers(options) {
884
+ const {
885
+ autoDefineBaseURL = "/auto-define/",
886
+ autoDefinePreload = [],
887
+ autoDefineMapper = null,
888
+ enhancers = [],
889
+ // New: raw overrides for AutoDefiner config (scanExisting, observeShadows, etc.)
890
+ autoDefineOverrides = null,
891
+ autoDefinePreferModule = true,
892
+ } = options;
893
+
894
+ // // Warn if assets not present (best-effort)
895
+ // try {
896
+ // if (typeof window !== "undefined") {
897
+ // const response = await fetch(`${autoDefineBaseURL}pds-icon.js`, {
898
+ // method: "HEAD",
899
+ // });
900
+ // if (!response.ok) {
901
+ // // No config available in this context, using console
902
+ // console.warn("⚠️ PDS components not found in auto-define directory.");
903
+ // }
904
+ // }
905
+ // } catch {}
906
+
907
+ // Merge defaults with user enhancers (user overrides by selector)
908
+ const mergedEnhancers = (() => {
909
+ const map = new Map();
910
+ (PDS.defaultEnhancers || []).forEach((e) => map.set(e.selector, e));
911
+ (enhancers || []).forEach((e) => map.set(e.selector, e));
912
+ return Array.from(map.values());
913
+ })();
914
+
915
+ // Setup AutoDefiner in browser context (it already observes shadow DOMs)
916
+ let autoDefiner = null;
917
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
918
+ // Dynamically import AutoDefiner to avoid Node/CJS interop at build time
919
+ let AutoDefinerCtor = null;
920
+ try {
921
+ const mod = await import("pure-web/auto-definer");
922
+ AutoDefinerCtor =
923
+ mod?.AutoDefiner || mod?.default?.AutoDefiner || mod?.default || null;
924
+ } catch (e) {
925
+ // No config available in this context, using console
926
+ console.warn("AutoDefiner not available:", e?.message || e);
927
+ }
928
+
929
+ const defaultMapper = (tag) => {
930
+ switch (tag) {
931
+ case "pds-tabpanel":
932
+ return "pds-tabstrip.js";
933
+ default:
934
+ return `${tag}.js`;
935
+ }
936
+ };
937
+
938
+ // Respect user overrides but never allow them to overwrite our mapper wrapper.
939
+ const { mapper: _overrideMapperIgnored, ...restAutoDefineOverrides } =
940
+ autoDefineOverrides && typeof autoDefineOverrides === "object"
941
+ ? autoDefineOverrides
942
+ : {};
943
+
944
+ const normalizedBaseURL = autoDefineBaseURL
945
+ ? __ensureTrailingSlash(
946
+ __ensureAbsoluteAssetURL(autoDefineBaseURL, {
947
+ preferModule: autoDefinePreferModule,
948
+ })
949
+ )
950
+ : autoDefineBaseURL;
951
+
952
+ const autoDefineConfig = {
953
+ baseURL: normalizedBaseURL,
954
+ predefine: autoDefinePreload,
955
+ scanExisting: true,
956
+ observeShadows: true,
957
+ patchAttachShadow: true,
958
+ debounceMs: 16,
959
+ enhancers: mergedEnhancers,
960
+ onError: (tag, err) => {
961
+ if (typeof tag === "string" && tag.startsWith("pds-")) {
962
+ // No config available in this context, using console
963
+ console.warn(
964
+ `⚠️ PDS component <${tag}> not found. Assets may not be installed.`
965
+ );
966
+ } else {
967
+ console.error(`❌ Auto-define error for <${tag}>:`, err);
968
+ }
969
+ },
970
+ // Apply all user overrides except mapper so we can still wrap it
971
+ ...restAutoDefineOverrides,
972
+ mapper: (tag) => {
973
+ // If already defined, do nothing
974
+ if (customElements.get(tag)) return null;
975
+
976
+ // If a custom mapper exists, let it try first; if it returns a non-value, fallback to default
977
+ if (typeof autoDefineMapper === "function") {
978
+ try {
979
+ const mapped = autoDefineMapper(tag);
980
+ if (mapped === undefined) {
981
+ return defaultMapper(tag);
982
+ }
983
+ return mapped;
984
+ } catch (e) {
985
+ // Be resilient: if custom mapper throws, fall back to default
986
+ console.warn(
987
+ "Custom autoDefine.mapper error; falling back to default:",
988
+ e?.message || e
989
+ );
990
+ return defaultMapper(tag);
991
+ }
992
+ }
993
+
994
+ // No custom mapper provided — use default
995
+ return defaultMapper(tag);
996
+ },
997
+ };
998
+
999
+ if (AutoDefinerCtor) {
1000
+ autoDefiner = new AutoDefinerCtor(autoDefineConfig);
1001
+ if (
1002
+ autoDefinePreload.length > 0 &&
1003
+ typeof AutoDefinerCtor.define === "function"
1004
+ ) {
1005
+ await AutoDefinerCtor.define(...autoDefinePreload, {
1006
+ baseURL: autoDefineBaseURL,
1007
+ mapper: autoDefineConfig.mapper,
1008
+ onError: autoDefineConfig.onError,
1009
+ });
1010
+ }
1011
+ }
1012
+ }
1013
+ // Rely on AutoDefiner to run enhancers across light and shadow DOMs
1014
+
1015
+ return { autoDefiner };
1016
+ }
1017
+
1018
+ async function live(config) {
1019
+ if (!config || typeof config !== "object") {
1020
+ throw new Error(
1021
+ "PDS.start({ mode: 'live', ... }) requires a valid configuration object"
1022
+ );
1023
+ }
1024
+
1025
+ // FOUC Prevention: Use constructable stylesheet for synchronous, immediate effect
1026
+ if (typeof document !== "undefined" && document.adoptedStyleSheets) {
1027
+ const css = /*css*/`
1028
+ html { opacity: 0; }
1029
+ html.pds-ready { opacity: 1; transition: opacity 0.3s ease-in; }
1030
+ `
1031
+ try {
1032
+ // Check if we've already added the FOUC prevention sheet
1033
+ const hasFoucSheet = document.adoptedStyleSheets.some(sheet => sheet._pdsFouc);
1034
+ if (!hasFoucSheet) {
1035
+ const foucSheet = new CSSStyleSheet();
1036
+ foucSheet.replaceSync(css);
1037
+ foucSheet._pdsFouc = true;
1038
+ document.adoptedStyleSheets = [foucSheet, ...document.adoptedStyleSheets];
1039
+ }
1040
+ } catch (e) {
1041
+ // Fallback for browsers that don't support constructable stylesheets
1042
+ // No config available here, using console
1043
+ console.warn("Constructable stylesheets not supported, using <style> tag fallback:", e);
1044
+ const existingFoucStyle = document.head.querySelector("style[data-pds-fouc]");
1045
+ if (!existingFoucStyle) {
1046
+ const foucStyle = document.createElement("style");
1047
+ foucStyle.setAttribute("data-pds-fouc", "");
1048
+ foucStyle.textContent = css;
1049
+ document.head.insertBefore(foucStyle, document.head.firstChild);
1050
+ }
1051
+ }
1052
+ }
1053
+
1054
+ // PDS is exposed on window at module init for both modes
1055
+
1056
+ // Extract runtime flags directly from unified config
1057
+ let applyGlobalStyles = config.applyGlobalStyles ?? true;
1058
+ let manageTheme = config.manageTheme ?? true;
1059
+ let themeStorageKey = config.themeStorageKey ?? "pure-ds-theme";
1060
+ let preloadStyles = config.preloadStyles ?? false;
1061
+ let criticalLayers = config.criticalLayers ?? ["tokens", "primitives"];
1062
+
1063
+ // New unified shape: autoDefine inside the first argument
1064
+ const cfgAuto = (config && config.autoDefine) || null;
1065
+ if (cfgAuto && typeof cfgAuto === "object") {
1066
+ // no-op here; resolved below for __setupAutoDefinerAndEnhancers
1067
+ }
1068
+
1069
+ try {
1070
+ // 1) Handle theme preference
1071
+ const { resolvedTheme, storedTheme } = __resolveThemeAndApply({
1072
+ manageTheme,
1073
+ themeStorageKey,
1074
+ });
1075
+
1076
+ // 2) Normalize first-arg API: support { preset, design, enhancers }
1077
+ const normalized = __normalizeInitConfig(config, {});
1078
+ const userEnhancers = normalized.enhancers;
1079
+ // Extract log function before cloning to avoid DataCloneError
1080
+ const { log: logFn, ...configToClone } = normalized.generatorConfig;
1081
+ const generatorConfig = structuredClone(configToClone);
1082
+ // Add log back after cloning
1083
+ generatorConfig.log = logFn;
1084
+ if (manageTheme) {
1085
+ generatorConfig.theme = resolvedTheme;
1086
+ }
1087
+
1088
+ const generator = new PDS.Generator(generatorConfig);
1089
+
1090
+ // 3) Load fonts from Google Fonts if needed (before applying styles)
1091
+ if (generatorConfig.design?.typography) {
1092
+ try {
1093
+ await loadTypographyFonts(generatorConfig.design.typography);
1094
+ } catch (ex) {
1095
+ generatorConfig?.log?.("warn", "Failed to load some fonts from Google Fonts:", ex);
1096
+ // Continue anyway - the system will fall back to default fonts
1097
+ }
1098
+ }
1099
+
1100
+ // 4) Preload critical styles synchronously to prevent flash
1101
+ if (preloadStyles && typeof window !== "undefined" && document.head) {
1102
+ try {
1103
+ // Generate critical CSS layers synchronously
1104
+ const criticalCSS = criticalLayers
1105
+ .map((layer) => {
1106
+ try {
1107
+ return generator.css?.[layer] || "";
1108
+ } catch (e) {
1109
+ generatorConfig?.log?.(
1110
+ "warn",
1111
+ `Failed to generate critical CSS for layer "${layer}":`,
1112
+ e
1113
+ );
1114
+ return "";
1115
+ }
1116
+ })
1117
+ .filter((css) => css.trim())
1118
+ .join("\n");
1119
+
1120
+ if (criticalCSS) {
1121
+ // Remove any existing PDS critical styles
1122
+ const existingCritical = document.head.querySelector(
1123
+ "style[data-pds-critical]"
1124
+ );
1125
+ if (existingCritical) {
1126
+ existingCritical.remove();
1127
+ }
1128
+
1129
+ // Inject critical CSS as a <style> tag in head
1130
+ const styleEl = document.createElement("style");
1131
+ styleEl.setAttribute("data-pds-critical", "");
1132
+ styleEl.textContent = criticalCSS;
1133
+
1134
+ // Insert early in head, but after charset/viewport if present
1135
+ const insertAfter = document.head.querySelector(
1136
+ 'meta[charset], meta[name="viewport"]'
1137
+ );
1138
+ if (insertAfter) {
1139
+ insertAfter.parentNode.insertBefore(
1140
+ styleEl,
1141
+ insertAfter.nextSibling
1142
+ );
1143
+ } else {
1144
+ document.head.insertBefore(styleEl, document.head.firstChild);
1145
+ }
1146
+ }
1147
+ } catch (error) {
1148
+ generatorConfig?.log?.("warn", "Failed to preload critical styles:", error);
1149
+ // Continue without critical styles - better than crashing
1150
+ }
1151
+ }
1152
+
1153
+ // Set the registry to use this designer
1154
+ PDS.registry.setDesigner(generator, {
1155
+ presetName: normalized.presetInfo?.name,
1156
+ });
1157
+
1158
+ // Apply styles globally if requested (default behavior)
1159
+ if (applyGlobalStyles) {
1160
+ await PDS.Generator.applyStyles(generator);
1161
+
1162
+ // Clean up critical styles after adoptedStyleSheets are applied
1163
+ if (typeof window !== "undefined") {
1164
+ // Small delay to ensure adoptedStyleSheets have taken effect
1165
+ setTimeout(() => {
1166
+ // Remove any previously inlined critical/preload styles that were unlayered
1167
+ const criticalStyle = document.head.querySelector(
1168
+ "style[data-pds-critical]"
1169
+ );
1170
+ if (criticalStyle) criticalStyle.remove();
1171
+
1172
+ const preloadStyle = document.head.querySelector(
1173
+ "style[data-pds-preload]"
1174
+ );
1175
+ if (preloadStyle) preloadStyle.remove();
1176
+
1177
+ // Remove legacy fallback runtime style tag if present
1178
+ const legacyRuntime = document.getElementById(
1179
+ "pds-runtime-stylesheet"
1180
+ );
1181
+ if (legacyRuntime) legacyRuntime.remove();
1182
+ }, 100);
1183
+ }
1184
+ }
1185
+
1186
+ // Note: auto-define base URL is used internally; no globals are written
1187
+
1188
+ // Derive a sensible default AutoDefiner base for LIVE mode too when not provided.
1189
+ // Use the normalized public asset root so live and static modes share the same directory layout.
1190
+ const assetRootURL = __resolveRuntimeAssetRoot(config);
1191
+
1192
+ let derivedAutoDefineBaseURL;
1193
+ if (cfgAuto && cfgAuto.baseURL) {
1194
+ derivedAutoDefineBaseURL = __ensureTrailingSlash(
1195
+ __ensureAbsoluteAssetURL(cfgAuto.baseURL, { preferModule: false })
1196
+ );
1197
+ } else {
1198
+ derivedAutoDefineBaseURL = `${assetRootURL}components/`;
1199
+ }
1200
+
1201
+ // 5) Set up AutoDefiner + run enhancers (defaults merged with user)
1202
+ let autoDefiner = null;
1203
+ try {
1204
+ const res = await __setupAutoDefinerAndEnhancers({
1205
+ autoDefineBaseURL: derivedAutoDefineBaseURL,
1206
+ autoDefinePreload:
1207
+ (cfgAuto && Array.isArray(cfgAuto.predefine) && cfgAuto.predefine) ||
1208
+ [],
1209
+ autoDefineMapper:
1210
+ (cfgAuto && typeof cfgAuto.mapper === "function" && cfgAuto.mapper) ||
1211
+ null,
1212
+ enhancers: userEnhancers,
1213
+ autoDefineOverrides: cfgAuto || null,
1214
+ autoDefinePreferModule: !(cfgAuto && cfgAuto.baseURL),
1215
+ });
1216
+ autoDefiner = res.autoDefiner;
1217
+ } catch (error) {
1218
+ generatorConfig?.log?.("error", "❌ Failed to initialize AutoDefiner/Enhancers:", error);
1219
+ }
1220
+
1221
+ // Determine resolved config to expose (generator stores input as options)
1222
+ const resolvedConfig = generator?.options || generatorConfig;
1223
+
1224
+ // Expose current config as frozen read-only on PDS, preserving input shape
1225
+ // Strip functions before cloning to avoid DataCloneError
1226
+ const cloneableConfig = __stripFunctions(config);
1227
+ PDS.currentConfig = Object.freeze({
1228
+ mode: "live",
1229
+ ...structuredClone(cloneableConfig),
1230
+ design: structuredClone(normalized.generatorConfig.design),
1231
+ preset: normalized.generatorConfig.preset,
1232
+ theme: resolvedTheme,
1233
+ });
1234
+
1235
+ // Emit event to notify that PDS is ready (unified)
1236
+ __emitPDSReady({
1237
+ mode: "live",
1238
+ generator,
1239
+ config: resolvedConfig,
1240
+ theme: resolvedTheme,
1241
+ autoDefiner,
1242
+ });
1243
+
1244
+ return {
1245
+ generator,
1246
+ config: resolvedConfig,
1247
+ theme: resolvedTheme,
1248
+ autoDefiner,
1249
+ };
1250
+ } catch (error) {
1251
+ // Emit error event
1252
+ PDS.dispatchEvent(
1253
+ new CustomEvent("pds:error", {
1254
+ detail: { error },
1255
+ })
1256
+ );
1257
+ throw error;
1258
+ }
1259
+ }
1260
+
1261
+ /**
1262
+ * Start PDS given a unified configuration and an explicit mode.
1263
+ *
1264
+ * @param {object} config - Unified configuration
1265
+ * @param {('live'|'static')} [config.mode='live'] - Runtime mode selector
1266
+ * @returns {Promise<any>} Live returns { generator, config, theme, autoDefiner }; Static returns { config, theme, autoDefiner }
1267
+ */
1268
+ async function start(config) {
1269
+ const mode = (config && config.mode) || "live";
1270
+ const { mode: _omit, ...rest } = config || {};
1271
+ if (mode === "static") return staticInit(rest);
1272
+ return live(rest);
1273
+ }
1274
+
1275
+ /** Primary unified entry point */
1276
+ PDS.start = start;
1277
+
1278
+
1279
+ /**
1280
+ * Initialize PDS in static mode with the same unified configuration shape as live mode.
1281
+ *
1282
+ * Shape:
1283
+ * PDS.start({
1284
+ * mode: 'static',
1285
+ * preset?: string,
1286
+ * design?: object,
1287
+ * autoDefine?: {
1288
+ * baseURL?: string,
1289
+ * predefine?: string[],
1290
+ * mapper?: (tag:string)=>string,
1291
+ * // plus any AutoDefiner flags
1292
+ * },
1293
+ * // static/runtime flags (optional)
1294
+ * applyGlobalStyles?: boolean,
1295
+ * manageTheme?: boolean,
1296
+ * themeStorageKey?: string,
1297
+ * staticPaths?: Record<string,string>
1298
+ * })
1299
+ */
1300
+ async function staticInit(config) {
1301
+ if (!config || typeof config !== "object") {
1302
+ throw new Error(
1303
+ "PDS.start({ mode: 'static', ... }) requires a valid configuration object"
1304
+ );
1305
+ }
1306
+
1307
+ const applyGlobalStyles = config.applyGlobalStyles ?? true;
1308
+ const manageTheme = config.manageTheme ?? true;
1309
+ const themeStorageKey = config.themeStorageKey ?? "pure-ds-theme";
1310
+ let staticPaths = config.staticPaths ?? {};
1311
+ const assetRootURL = __resolveRuntimeAssetRoot(config);
1312
+ const cfgAuto = (config && config.autoDefine) || null;
1313
+ let autoDefineBaseURL;
1314
+ if (cfgAuto && cfgAuto.baseURL) {
1315
+ autoDefineBaseURL = __ensureTrailingSlash(
1316
+ __ensureAbsoluteAssetURL(cfgAuto.baseURL, { preferModule: false })
1317
+ );
1318
+ } else {
1319
+ autoDefineBaseURL = `${assetRootURL}components/`;
1320
+ }
1321
+ const autoDefinePreload =
1322
+ (cfgAuto && Array.isArray(cfgAuto.predefine) && cfgAuto.predefine) || [];
1323
+ const autoDefineMapper =
1324
+ (cfgAuto && typeof cfgAuto.mapper === "function" && cfgAuto.mapper) || null;
1325
+
1326
+ try {
1327
+ // 1) Theme
1328
+ const { resolvedTheme } = __resolveThemeAndApply({
1329
+ manageTheme,
1330
+ themeStorageKey,
1331
+ });
1332
+
1333
+ // Normalize first-arg to allow { preset, design, enhancers }
1334
+ const normalized = __normalizeInitConfig(config, {});
1335
+ const userEnhancers = normalized.enhancers;
1336
+
1337
+ // 2) Derive static asset URLs from the normalized public root
1338
+ const baseStaticPaths = {
1339
+ tokens: `${assetRootURL}styles/pds-tokens.css.js`,
1340
+ primitives: `${assetRootURL}styles/pds-primitives.css.js`,
1341
+ components: `${assetRootURL}styles/pds-components.css.js`,
1342
+ utilities: `${assetRootURL}styles/pds-utilities.css.js`,
1343
+ styles: `${assetRootURL}styles/pds-styles.css.js`,
1344
+ };
1345
+ staticPaths = { ...baseStaticPaths, ...staticPaths };
1346
+
1347
+ // 3) Static mode registry
1348
+ PDS.registry.setStaticMode(staticPaths);
1349
+
1350
+ // 4) Apply global static styles if requested
1351
+ if (applyGlobalStyles && typeof document !== "undefined") {
1352
+ try {
1353
+ // Always adopt from exported css.js modules so static mode loads from JS exports
1354
+ const stylesSheet = await PDS.registry.getStylesheet("styles");
1355
+ if (stylesSheet) {
1356
+ // Tag and adopt alongside existing non-PDS sheets
1357
+ stylesSheet._pds = true;
1358
+ const others = (document.adoptedStyleSheets || []).filter(
1359
+ (s) => s._pds !== true
1360
+ );
1361
+ document.adoptedStyleSheets = [...others, stylesSheet];
1362
+ }
1363
+ } catch (e) {
1364
+ // No config available in static mode, using console
1365
+ console.warn("Failed to apply static styles:", e);
1366
+ }
1367
+ }
1368
+
1369
+ // 5) AutoDefiner + Enhancers
1370
+ let autoDefiner = null;
1371
+ try {
1372
+ const res = await __setupAutoDefinerAndEnhancers({
1373
+ autoDefineBaseURL,
1374
+ autoDefinePreload,
1375
+ autoDefineMapper,
1376
+ enhancers: userEnhancers,
1377
+ autoDefineOverrides: cfgAuto || null,
1378
+ autoDefinePreferModule: !(cfgAuto && cfgAuto.baseURL),
1379
+ });
1380
+ autoDefiner = res.autoDefiner;
1381
+ } catch (error) {
1382
+ // No config available in static mode, using console
1383
+ console.error(
1384
+ "❌ Failed to initialize AutoDefiner/Enhancers (static):",
1385
+ error
1386
+ );
1387
+ }
1388
+
1389
+ // Expose current config as frozen read-only on PDS, preserving input shape
1390
+ // Strip functions before cloning to avoid DataCloneError
1391
+ const cloneableConfig = __stripFunctions(config);
1392
+ PDS.currentConfig = Object.freeze({
1393
+ mode: "static",
1394
+ ...structuredClone(cloneableConfig),
1395
+ design: structuredClone(normalized.generatorConfig.design),
1396
+ preset: normalized.generatorConfig.preset,
1397
+ theme: resolvedTheme,
1398
+ });
1399
+
1400
+ // 6) Emit ready event (unified)
1401
+ __emitPDSReady({
1402
+ mode: "static",
1403
+ config: normalized.generatorConfig,
1404
+ theme: resolvedTheme,
1405
+ autoDefiner,
1406
+ });
1407
+ return {
1408
+ config: normalized.generatorConfig,
1409
+ theme: resolvedTheme,
1410
+ autoDefiner,
1411
+ };
1412
+ } catch (error) {
1413
+ PDS.dispatchEvent(new CustomEvent("pds:error", { detail: { error } }));
1414
+ throw error;
1415
+ }
1416
+ }
1417
+
1418
+ // Note: PDS.static is not exported. Use PDS.start({ mode: 'static', ... }).
1419
+
1420
+ /**
1421
+ * Change the current theme programmatically.
1422
+ * This updates localStorage, the data-theme attribute, and regenerates styles if in live mode.
1423
+ *
1424
+ * @param {string} theme - Theme to apply: 'light', 'dark', or 'system'
1425
+ * @param {object} [options] - Optional settings
1426
+ * @param {string} [options.storageKey='pure-ds-theme'] - localStorage key for theme preference
1427
+ * @param {boolean} [options.persist=true] - Whether to save to localStorage
1428
+ * @returns {Promise<string>} The resolved theme ('light' or 'dark')
1429
+ */
1430
+ async function setTheme(theme, options = {}) {
1431
+ const { storageKey = "pure-ds-theme", persist = true } = options;
1432
+
1433
+ if (!["light", "dark", "system"].includes(theme)) {
1434
+ throw new Error(
1435
+ `Invalid theme "${theme}". Must be "light", "dark", or "system".`
1436
+ );
1437
+ }
1438
+
1439
+ if (typeof window === "undefined") {
1440
+ return theme === "system" ? "light" : theme;
1441
+ }
1442
+
1443
+ let resolvedTheme = theme;
1444
+
1445
+ // Resolve 'system' to actual preference
1446
+ if (theme === "system") {
1447
+ const prefersDark =
1448
+ window.matchMedia &&
1449
+ window.matchMedia("(prefers-color-scheme: dark)").matches;
1450
+ resolvedTheme = prefersDark ? "dark" : "light";
1451
+ }
1452
+
1453
+ // Update data-theme attribute
1454
+ document.documentElement.setAttribute("data-theme", resolvedTheme);
1455
+
1456
+ // Persist to localStorage if requested
1457
+ if (persist) {
1458
+ localStorage.setItem(storageKey, theme);
1459
+ }
1460
+
1461
+ // If we're in live mode, regenerate styles with new theme
1462
+ if (PDS.registry.isLive && PDS.registry.hasDesigner) {
1463
+ try {
1464
+ const currentDesigner = PDS.registry._designer; // Access internal designer
1465
+ if (currentDesigner && currentDesigner.configure) {
1466
+ // Update the designer's config with new theme
1467
+ const newConfig = { ...currentDesigner.config, theme: resolvedTheme };
1468
+ currentDesigner.configure(newConfig);
1469
+
1470
+ // Reapply styles
1471
+ await PDS.Generator.applyStyles(currentDesigner);
1472
+ }
1473
+ } catch (error) {
1474
+ currentDesigner?.options?.log?.("warn", "Failed to update styles for new theme:", error);
1475
+ }
1476
+ }
1477
+
1478
+ // Emit theme change event (unified)
1479
+ PDS.dispatchEvent(
1480
+ new CustomEvent("pds:theme:changed", {
1481
+ detail: {
1482
+ theme: resolvedTheme,
1483
+ requested: theme,
1484
+ source: "programmatic",
1485
+ },
1486
+ })
1487
+ );
1488
+
1489
+ return resolvedTheme;
1490
+ }
1491
+
1492
+ /** Change the current theme programmatically */
1493
+ PDS.setTheme = setTheme;
1494
+
1495
+ /**
1496
+ * Preload minimal CSS to prevent flash of unstyled content.
1497
+ * Call this BEFORE any DOM content is rendered for best results.
1498
+ * This is a lightweight alternative to full PDS.start({ mode: 'live' }) initialization.
1499
+ *
1500
+ * @param {object} config - Minimal PDS config (colors at minimum)
1501
+ * @param {object} [options] - Optional settings
1502
+ * @param {string} [options.theme] - Theme to generate for ('light', 'dark', or 'system')
1503
+ * @param {string[]} [options.layers=['tokens']] - Which CSS layers to preload
1504
+ * @returns {void}
1505
+ *
1506
+ * @example
1507
+ * ```html
1508
+ * <script type="module">
1509
+ * import { PDS } from 'pure-ds/pds-core';
1510
+ * // Call immediately to prevent flash
1511
+ * PDS.preloadCritical({ colors: { primary: '#007acc' } });
1512
+ * </script>
1513
+ * ```
1514
+ */
1515
+ function preloadCritical(config, options = {}) {
1516
+ if (typeof window === "undefined" || !document.head || !config) {
1517
+ return;
1518
+ }
1519
+
1520
+ const { theme, layers = ["tokens"] } = options;
1521
+
1522
+ try {
1523
+ // Resolve theme quickly
1524
+ let resolvedTheme = theme || "light";
1525
+ if (theme === "system" || !theme) {
1526
+ const prefersDark =
1527
+ window.matchMedia &&
1528
+ window.matchMedia("(prefers-color-scheme: dark)").matches;
1529
+ resolvedTheme = prefersDark ? "dark" : "light";
1530
+ }
1531
+
1532
+ // Set theme attribute immediately
1533
+ document.documentElement.setAttribute("data-theme", resolvedTheme);
1534
+
1535
+ // Generate minimal CSS synchronously
1536
+ // Normalize config to ensure design property exists
1537
+ const tempConfig = config.design
1538
+ ? { ...config, theme: resolvedTheme }
1539
+ : { design: config, theme: resolvedTheme };
1540
+ const tempGenerator = new PDS.Generator(tempConfig);
1541
+
1542
+ const criticalCSS = layers
1543
+ .map((layer) => {
1544
+ try {
1545
+ return tempGenerator.css?.[layer] || "";
1546
+ } catch (e) {
1547
+ return "";
1548
+ }
1549
+ })
1550
+ .filter((css) => css.trim())
1551
+ .join("\n");
1552
+
1553
+ if (criticalCSS) {
1554
+ // Remove any existing critical styles
1555
+ const existing = document.head.querySelector("style[data-pds-preload]");
1556
+ if (existing) existing.remove();
1557
+
1558
+ // Inject immediately
1559
+ const styleEl = document.createElement("style");
1560
+ styleEl.setAttribute("data-pds-preload", "");
1561
+ styleEl.textContent = criticalCSS;
1562
+
1563
+ // Insert as early as possible
1564
+ document.head.insertBefore(styleEl, document.head.firstChild);
1565
+ }
1566
+ } catch (error) {
1567
+ // Fail silently - better than blocking page load
1568
+ // No config available in this context, using console
1569
+ console.warn("PDS preload failed:", error);
1570
+ }
1571
+ }
1572
+
1573
+ /** Preload minimal CSS to prevent flash of unstyled content */
1574
+ PDS.preloadCritical = preloadCritical;
1575
+
1576
+ // Note: PDS object is not frozen to allow runtime properties like currentConfig
1577
+ // to be set during initialization. The config object itself is frozen for immutability.
1578
+
1579
+ export { PDS, validateDesign };