@pure-ds/core 0.6.9 → 0.6.11

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 (90) hide show
  1. package/custom-elements.json +865 -35
  2. package/dist/types/pds.d.ts +31 -0
  3. package/dist/types/public/assets/js/pds-manager.d.ts +100 -2
  4. package/dist/types/public/assets/js/pds-manager.d.ts.map +1 -1
  5. package/dist/types/public/assets/js/pds.d.ts.map +1 -1
  6. package/dist/types/public/assets/pds/components/pds-form.d.ts.map +1 -1
  7. package/dist/types/public/assets/pds/components/pds-live-converter.d.ts +8 -0
  8. package/dist/types/public/assets/pds/components/pds-live-converter.d.ts.map +1 -0
  9. package/dist/types/public/assets/pds/components/pds-live-edit.d.ts +1 -195
  10. package/dist/types/public/assets/pds/components/pds-live-edit.d.ts.map +1 -1
  11. package/dist/types/public/assets/pds/components/pds-live-importer.d.ts +2 -0
  12. package/dist/types/public/assets/pds/components/pds-live-importer.d.ts.map +1 -0
  13. package/dist/types/public/assets/pds/components/pds-live-template-canvas.d.ts +2 -0
  14. package/dist/types/public/assets/pds/components/pds-live-template-canvas.d.ts.map +1 -0
  15. package/dist/types/public/assets/pds/components/pds-omnibox.d.ts +0 -2
  16. package/dist/types/public/assets/pds/components/pds-omnibox.d.ts.map +1 -1
  17. package/dist/types/public/assets/pds/components/pds-scrollrow.d.ts +20 -0
  18. package/dist/types/public/assets/pds/components/pds-scrollrow.d.ts.map +1 -1
  19. package/dist/types/public/assets/pds/components/pds-toaster.d.ts +1 -1
  20. package/dist/types/public/assets/pds/components/pds-toaster.d.ts.map +1 -1
  21. package/dist/types/public/assets/pds/components/pds-treeview.d.ts +37 -0
  22. package/dist/types/public/assets/pds/components/pds-treeview.d.ts.map +1 -0
  23. package/dist/types/src/js/common/toast.d.ts +8 -0
  24. package/dist/types/src/js/common/toast.d.ts.map +1 -1
  25. package/dist/types/src/js/pds-core/pds-config.d.ts +1306 -13
  26. package/dist/types/src/js/pds-core/pds-config.d.ts.map +1 -1
  27. package/dist/types/src/js/pds-core/pds-enhancers-meta.d.ts.map +1 -1
  28. package/dist/types/src/js/pds-core/pds-enhancers.d.ts.map +1 -1
  29. package/dist/types/src/js/pds-core/pds-generator.d.ts.map +1 -1
  30. package/dist/types/src/js/pds-core/pds-live.d.ts +2 -1
  31. package/dist/types/src/js/pds-core/pds-live.d.ts.map +1 -1
  32. package/dist/types/src/js/pds-core/pds-ontology.d.ts.map +1 -1
  33. package/dist/types/src/js/pds-core/pds-start-helpers.d.ts +1 -4
  34. package/dist/types/src/js/pds-core/pds-start-helpers.d.ts.map +1 -1
  35. package/dist/types/src/js/pds-live-manager/conversion-service.d.ts +66 -0
  36. package/dist/types/src/js/pds-live-manager/conversion-service.d.ts.map +1 -0
  37. package/dist/types/src/js/pds-live-manager/import-contract.d.ts +15 -0
  38. package/dist/types/src/js/pds-live-manager/import-contract.d.ts.map +1 -0
  39. package/dist/types/src/js/pds-live-manager/import-history-service.d.ts +32 -0
  40. package/dist/types/src/js/pds-live-manager/import-history-service.d.ts.map +1 -0
  41. package/dist/types/src/js/pds-live-manager/import-service.d.ts +21 -0
  42. package/dist/types/src/js/pds-live-manager/import-service.d.ts.map +1 -0
  43. package/dist/types/src/js/pds-live-manager/template-service.d.ts +17 -0
  44. package/dist/types/src/js/pds-live-manager/template-service.d.ts.map +1 -0
  45. package/dist/types/src/js/pds-manager.d.ts +4 -0
  46. package/dist/types/src/js/pds.d.ts.map +1 -1
  47. package/package.json +7 -3
  48. package/packages/pds-cli/README.md +51 -0
  49. package/packages/pds-cli/bin/pds-import.js +176 -0
  50. package/packages/pds-cli/bin/pds-static.js +31 -1
  51. package/packages/pds-cli/bin/postinstall.mjs +17 -8
  52. package/public/assets/js/app.js +23 -147
  53. package/public/assets/js/pds-manager.js +481 -248
  54. package/public/assets/js/pds.js +16 -16
  55. package/public/assets/pds/components/pds-form.js +124 -27
  56. package/public/assets/pds/components/pds-live-converter.js +47 -0
  57. package/public/assets/pds/components/pds-live-edit.js +1626 -211
  58. package/public/assets/pds/components/pds-live-importer.js +772 -0
  59. package/public/assets/pds/components/pds-live-template-canvas.js +171 -0
  60. package/public/assets/pds/components/pds-omnibox.js +146 -20
  61. package/public/assets/pds/components/pds-scrollrow.js +56 -1
  62. package/public/assets/pds/components/pds-toaster.js +50 -5
  63. package/public/assets/pds/components/pds-treeview.js +972 -0
  64. package/public/assets/pds/custom-elements.json +865 -35
  65. package/public/assets/pds/pds-css-complete.json +7 -7
  66. package/public/assets/pds/pds.css-data.json +5 -35
  67. package/public/assets/pds/templates/commerce-scroll-explorer.html +115 -0
  68. package/public/assets/pds/templates/content-brand-showcase.html +110 -0
  69. package/public/assets/pds/templates/feedback-ops-dashboard.html +91 -0
  70. package/public/assets/pds/templates/release-readiness-radar.html +69 -0
  71. package/public/assets/pds/templates/support-command-center.html +92 -0
  72. package/public/assets/pds/templates/templates.json +53 -0
  73. package/public/assets/pds/templates/workspace-settings-lab.html +131 -0
  74. package/public/assets/pds/vscode-custom-data.json +54 -4
  75. package/readme.md +34 -0
  76. package/src/js/pds-core/pds-config.js +831 -40
  77. package/src/js/pds-core/pds-enhancers-meta.js +11 -0
  78. package/src/js/pds-core/pds-enhancers.js +259 -5
  79. package/src/js/pds-core/pds-generator.js +353 -52
  80. package/src/js/pds-core/pds-live.js +630 -15
  81. package/src/js/pds-core/pds-ontology.js +6 -0
  82. package/src/js/pds-core/pds-start-helpers.js +14 -6
  83. package/src/js/pds-live-manager/conversion-service.js +3136 -0
  84. package/src/js/pds-live-manager/import-contract.js +57 -0
  85. package/src/js/pds-live-manager/import-history-service.js +145 -0
  86. package/src/js/pds-live-manager/import-service.js +255 -0
  87. package/src/js/pds-live-manager/tailwind-conversion-rules.json +383 -0
  88. package/src/js/pds-live-manager/template-service.js +170 -0
  89. package/src/js/pds.d.ts +31 -0
  90. package/src/js/pds.js +71 -60
@@ -689,14 +689,16 @@ export class Generator {
689
689
  const validBase = Number.isFinite(Number(baseBorderWidth))
690
690
  ? Number(baseBorderWidth)
691
691
  : enums.BorderWidths.medium;
692
- const toPx = (value) => `${Math.max(0, Math.round(value * 100) / 100)}px`;
692
+ // Snap to whole CSS pixels to avoid subpixel border widths that can render
693
+ // identically across browsers/DPIs (e.g. 0.5px and 1.5px both appearing as 1px).
694
+ const toRenderablePx = (value) => `${Math.max(1, Math.ceil(value))}px`;
693
695
 
694
696
  // Generate a derived border width scale based on configured base width
695
697
  return {
696
- hairline: toPx(validBase * 0.25),
697
- thin: toPx(validBase * 0.5),
698
- medium: toPx(validBase),
699
- thick: toPx(validBase * 1.5),
698
+ hairline: toRenderablePx(validBase * 0.25),
699
+ thin: toRenderablePx(validBase * 0.5),
700
+ medium: toRenderablePx(validBase),
701
+ thick: toRenderablePx(validBase * 1.5),
700
702
  };
701
703
  }
702
704
 
@@ -805,7 +807,6 @@ export class Generator {
805
807
 
806
808
  #generateLayoutTokens(layoutConfig) {
807
809
  const {
808
- maxWidth = 1200,
809
810
  containerPadding = 16,
810
811
  breakpoints = {
811
812
  sm: 640,
@@ -815,10 +816,19 @@ export class Generator {
815
816
  },
816
817
  } = layoutConfig;
817
818
 
818
- const resolvedMaxWidths = this.#resolveLayoutMaxWidths(layoutConfig);
819
+ const hasExplicitMaxWidth =
820
+ this.#hasDefinedConfigValue(layoutConfig, "maxWidth");
821
+
822
+ const explicitMaxWidthValue = layoutConfig.maxWidth;
823
+
824
+ const resolvedMaxWidths = this.#resolveLayoutMaxWidths(layoutConfig, {
825
+ emitFallbacks: false,
826
+ });
819
827
 
820
828
  return {
821
- maxWidth: this.#formatLength(maxWidth, "1200px"),
829
+ maxWidth: hasExplicitMaxWidth
830
+ ? this.#formatLength(explicitMaxWidthValue, "1200px")
831
+ : undefined,
822
832
  maxWidthSm: resolvedMaxWidths.sm,
823
833
  maxWidthMd: resolvedMaxWidths.md,
824
834
  maxWidthLg: resolvedMaxWidths.lg,
@@ -841,7 +851,8 @@ export class Generator {
841
851
  };
842
852
  }
843
853
 
844
- #resolveLayoutMaxWidths(layoutConfig = {}) {
854
+ #resolveLayoutMaxWidths(layoutConfig = {}, options = {}) {
855
+ const { emitFallbacks = true } = options;
845
856
  const defaultBreakpoints = {
846
857
  sm: 640,
847
858
  md: 768,
@@ -849,15 +860,29 @@ export class Generator {
849
860
  xl: 1280,
850
861
  };
851
862
 
852
- const {
853
- maxWidths = {},
854
- maxWidth = 1200,
855
- containerPadding = 16,
856
- breakpoints = defaultBreakpoints,
857
- } = layoutConfig || {};
863
+ const { maxWidths = {}, containerPadding = 16, breakpoints = defaultBreakpoints } =
864
+ layoutConfig || {};
865
+
866
+ const hasExplicitMaxWidth =
867
+ this.#hasDefinedConfigValue(layoutConfig, "maxWidth");
868
+
869
+ const hasExplicitMaxWidths = ["sm", "md", "lg", "xl"].some((key) =>
870
+ this.#hasDefinedConfigValue(maxWidths, key),
871
+ );
872
+
873
+ if (!emitFallbacks && !hasExplicitMaxWidth && !hasExplicitMaxWidths) {
874
+ return {
875
+ sm: undefined,
876
+ md: undefined,
877
+ lg: undefined,
878
+ xl: undefined,
879
+ };
880
+ }
881
+
882
+ const maxWidthValue = layoutConfig?.maxWidth;
858
883
 
859
884
  const paddingValue = this.#toNumber(containerPadding, 16);
860
- const baseMaxWidth = this.#toNumber(maxWidth, defaultBreakpoints.xl);
885
+ const baseMaxWidth = this.#toNumber(maxWidthValue, defaultBreakpoints.xl);
861
886
 
862
887
  const resolvedBreakpoints = {
863
888
  sm: this.#toNumber(breakpoints.sm, defaultBreakpoints.sm),
@@ -888,6 +913,15 @@ export class Generator {
888
913
  };
889
914
  }
890
915
 
916
+ #hasDefinedConfigValue(source, key) {
917
+ if (!source || typeof source !== "object") return false;
918
+ if (!Object.prototype.hasOwnProperty.call(source, key)) return false;
919
+ const value = source[key];
920
+ if (value === undefined || value === null) return false;
921
+ if (typeof value === "string" && value.trim().length === 0) return false;
922
+ return true;
923
+ }
924
+
891
925
  #formatLength(value, fallback) {
892
926
  if (typeof value === "number" && Number.isFinite(value)) {
893
927
  return `${value}px`;
@@ -1186,6 +1220,10 @@ export class Generator {
1186
1220
  // Convert camelCase keys to kebab-case
1187
1221
  const kebabKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
1188
1222
 
1223
+ if (value === undefined || value === null) {
1224
+ return;
1225
+ }
1226
+
1189
1227
  // Skip breakpoints object - it's used in JS but doesn't belong in CSS variables
1190
1228
  // Breakpoints are used in @media queries, not as CSS custom properties
1191
1229
  if (key === "breakpoints") {
@@ -1279,7 +1317,7 @@ export class Generator {
1279
1317
  smartLines.push(`\n`);
1280
1318
  }
1281
1319
 
1282
- const semantic = ` --color-text-primary: var(--color-gray-100);\n --color-text-secondary: var(--color-gray-300);\n --color-text-muted: var(--color-gray-400);\n --color-border: var(--color-gray-700);\n --color-input-bg: var(--color-gray-800);\n --color-input-disabled-bg: var(--color-gray-900);\n --color-input-disabled-text: var(--color-gray-600);\n --color-code-bg: var(--color-gray-800);\n`;
1320
+ const semantic = ` --color-text-primary: var(--color-gray-100);\n --color-text-secondary: var(--color-gray-300);\n --color-text-muted: var(--color-gray-600);\n --color-border: var(--color-gray-700);\n --color-input-bg: var(--color-gray-800);\n --color-input-disabled-bg: var(--color-gray-900);\n --color-input-disabled-text: var(--color-gray-600);\n --color-code-bg: var(--color-gray-800);\n`;
1283
1321
 
1284
1322
  const backdrop = ` /* Backdrop tokens - dark mode */\n --backdrop-bg: linear-gradient(\n 135deg,\n rgba(0, 0, 0, 0.6),\n rgba(0, 0, 0, 0.4)\n );\n --backdrop-blur: 10px;\n --backdrop-saturate: 120%;\n --backdrop-brightness: 0.7;\n --backdrop-filter: blur(var(--backdrop-blur)) saturate(var(--backdrop-saturate)) brightness(var(--backdrop-brightness));\n --backdrop-opacity: 1;\n \n /* Legacy alias for backwards compatibility */\n --backdrop-background: var(--backdrop-bg);\n`;
1285
1323
 
@@ -1377,7 +1415,7 @@ export class Generator {
1377
1415
  const semantic = [
1378
1416
  ` --color-text-primary: var(--color-gray-100);\n`,
1379
1417
  ` --color-text-secondary: var(--color-gray-300);\n`,
1380
- ` --color-text-muted: var(--color-gray-400);\n`,
1418
+ ` --color-text-muted: var(--color-gray-600);\n`,
1381
1419
  ` --color-border: var(--color-gray-700);\n`,
1382
1420
  ` --color-input-bg: var(--color-gray-800);\n`,
1383
1421
  ` --color-input-disabled-bg: var(--color-gray-900);\n`,
@@ -1605,7 +1643,7 @@ html[data-theme="dark"] .liquid-glass {
1605
1643
  .border-gradient {
1606
1644
  border: var(--border-width-medium) solid transparent;
1607
1645
  background:
1608
- linear-gradient(var(--color-surface-base), var(--color-surface-base)) padding-box,
1646
+ linear-gradient(var(--border-gradient-fill, var(--color-surface-base)), var(--border-gradient-fill, var(--color-surface-base))) padding-box,
1609
1647
  linear-gradient(var(--gradient-angle, 135deg),
1610
1648
  var(--color-primary-400),
1611
1649
  var(--color-accent-400)
@@ -1616,7 +1654,7 @@ html[data-theme="dark"] .liquid-glass {
1616
1654
  .border-gradient-primary {
1617
1655
  border: var(--border-width-medium) solid transparent;
1618
1656
  background:
1619
- linear-gradient(var(--color-surface-base), var(--color-surface-base)) padding-box,
1657
+ linear-gradient(var(--border-gradient-fill, var(--color-surface-base)), var(--border-gradient-fill, var(--color-surface-base))) padding-box,
1620
1658
  linear-gradient(var(--gradient-angle, 135deg),
1621
1659
  var(--color-primary-300),
1622
1660
  var(--color-primary-600)
@@ -1626,7 +1664,7 @@ html[data-theme="dark"] .liquid-glass {
1626
1664
  .border-gradient-accent {
1627
1665
  border: var(--border-width-medium) solid transparent;
1628
1666
  background:
1629
- linear-gradient(var(--color-surface-base), var(--color-surface-base)) padding-box,
1667
+ linear-gradient(var(--border-gradient-fill, var(--color-surface-base)), var(--border-gradient-fill, var(--color-surface-base))) padding-box,
1630
1668
  linear-gradient(var(--gradient-angle, 135deg),
1631
1669
  var(--color-accent-300),
1632
1670
  var(--color-accent-600)
@@ -1636,7 +1674,7 @@ html[data-theme="dark"] .liquid-glass {
1636
1674
  .border-gradient-secondary {
1637
1675
  border: var(--border-width-medium) solid transparent;
1638
1676
  background:
1639
- linear-gradient(var(--color-surface-base), var(--color-surface-base)) padding-box,
1677
+ linear-gradient(var(--border-gradient-fill, var(--color-surface-base)), var(--border-gradient-fill, var(--color-surface-base))) padding-box,
1640
1678
  linear-gradient(var(--gradient-angle, 135deg),
1641
1679
  var(--color-secondary-300),
1642
1680
  var(--color-secondary-600)
@@ -1647,7 +1685,7 @@ html[data-theme="dark"] .liquid-glass {
1647
1685
  .border-gradient-soft {
1648
1686
  border: var(--border-width-thin) solid transparent;
1649
1687
  background:
1650
- linear-gradient(var(--color-surface-base), var(--color-surface-base)) padding-box,
1688
+ linear-gradient(var(--border-gradient-fill, var(--color-surface-base)), var(--border-gradient-fill, var(--color-surface-base))) padding-box,
1651
1689
  linear-gradient(var(--gradient-angle, 135deg),
1652
1690
  var(--color-primary-400),
1653
1691
  var(--color-accent-400)
@@ -1657,7 +1695,7 @@ html[data-theme="dark"] .liquid-glass {
1657
1695
  .border-gradient-medium {
1658
1696
  border: var(--border-width-medium) solid transparent;
1659
1697
  background:
1660
- linear-gradient(var(--color-surface-base), var(--color-surface-base)) padding-box,
1698
+ linear-gradient(var(--border-gradient-fill, var(--color-surface-base)), var(--border-gradient-fill, var(--color-surface-base))) padding-box,
1661
1699
  linear-gradient(var(--gradient-angle, 135deg),
1662
1700
  var(--color-primary-400),
1663
1701
  var(--color-accent-400)
@@ -1667,7 +1705,7 @@ html[data-theme="dark"] .liquid-glass {
1667
1705
  .border-gradient-strong {
1668
1706
  border: var(--border-width-thick) solid transparent;
1669
1707
  background:
1670
- linear-gradient(var(--color-surface-base), var(--color-surface-base)) padding-box,
1708
+ linear-gradient(var(--border-gradient-fill, var(--color-surface-base)), var(--border-gradient-fill, var(--color-surface-base))) padding-box,
1671
1709
  linear-gradient(var(--gradient-angle, 135deg),
1672
1710
  var(--color-primary-400),
1673
1711
  var(--color-accent-400)
@@ -1691,7 +1729,7 @@ html[data-theme="dark"] .liquid-glass {
1691
1729
  .border-gradient-glow {
1692
1730
  border: var(--border-width-medium) solid transparent;
1693
1731
  background:
1694
- linear-gradient(var(--color-surface-base), var(--color-surface-base)) padding-box,
1732
+ linear-gradient(var(--border-gradient-fill, var(--color-surface-base)), var(--border-gradient-fill, var(--color-surface-base))) padding-box,
1695
1733
  linear-gradient(135deg,
1696
1734
  var(--color-primary-400),
1697
1735
  var(--color-accent-400)
@@ -1945,7 +1983,7 @@ html[data-theme="dark"] .liquid-glass {
1945
1983
  #generateFormStyles() {
1946
1984
  const {
1947
1985
  shape = {},
1948
- gap,
1986
+ spatialRhythm = {},
1949
1987
  inputPadding,
1950
1988
  buttonPadding,
1951
1989
  focusRingWidth,
@@ -1963,14 +2001,15 @@ html[data-theme="dark"] .liquid-glass {
1963
2001
  ? (enums.BorderWidths[shape.borderWidth] ?? null)
1964
2002
  : null;
1965
2003
 
1966
- const inputPaddingValue = inputPadding || 0.75;
1967
- const buttonPaddingValue = buttonPadding || 1.0;
2004
+ const inputPaddingValue = spatialRhythm.inputPadding ?? inputPadding ?? 0.75;
2005
+ const buttonPaddingValue =
2006
+ spatialRhythm.buttonPadding ?? buttonPadding ?? 1.0;
1968
2007
  const focusWidth = focusRingWidth || 3;
1969
2008
  const borderWidth =
1970
2009
  borderWidthThin || shapeBorderWidth || enums.BorderWidths.thin;
1971
- const gapValue = gap || 1.0;
1972
- const sectionSpacingValue = sectionSpacing || 2.0;
1973
- const minButtonHeight = buttonMinHeight || 44;
2010
+ const sectionSpacingValue =
2011
+ spatialRhythm.sectionSpacing ?? sectionSpacing ?? 2.0;
2012
+ const minButtonHeight = buttonMinHeight || 30;
1974
2013
  const minInputHeight = inputMinHeight || 40;
1975
2014
 
1976
2015
  return /*css*/ `/* Mobile-First Form Styles - Generated from Design Config */
@@ -2235,24 +2274,31 @@ input[type="range"]:active::-moz-range-thumb {
2235
2274
 
2236
2275
  input[type="color"] {
2237
2276
  -webkit-appearance: none;
2277
+ appearance: none;
2238
2278
  padding: 0;
2239
- width: 3rem;
2240
- height: 3rem;
2241
- border-radius: 0.75rem; /* your radius */
2242
- overflow: hidden; /* important */
2279
+ width: calc(var(--spacing-8) + var(--spacing-1));
2280
+ height: calc(var(--spacing-8) + var(--spacing-1));
2281
+ min-height: auto;
2282
+ border-radius: var(--radius-sm);
2283
+ border: var(--border-width-thin) solid var(--color-border);
2284
+ overflow: hidden;
2243
2285
  cursor: pointer;
2286
+ background: transparent;
2244
2287
 
2245
- /* The wrapper */
2246
2288
  &::-webkit-color-swatch-wrapper {
2247
2289
  padding: 0;
2248
2290
  border-radius: inherit;
2249
2291
  }
2250
2292
 
2251
- /* The swatch (the actual color box) */
2252
2293
  &::-webkit-color-swatch {
2253
2294
  border: none;
2254
2295
  border-radius: inherit;
2255
2296
  }
2297
+
2298
+ &::-moz-color-swatch {
2299
+ border: none;
2300
+ border-radius: inherit;
2301
+ }
2256
2302
  }
2257
2303
 
2258
2304
  /* Button-style checkbox inputs outside of fieldsets */
@@ -2555,6 +2601,116 @@ label[data-toggle] {
2555
2601
  }
2556
2602
  }
2557
2603
 
2604
+ /* Color input enhancement shell - applied by enhanceColorInput on label[data-color] */
2605
+ label[data-color] {
2606
+ display: grid;
2607
+ gap: var(--spacing-2);
2608
+
2609
+ .color-control {
2610
+ display: inline-flex;
2611
+ align-items: center;
2612
+ gap: var(--spacing-3);
2613
+ width: fit-content;
2614
+ min-height: var(--input-min-height, 40px);
2615
+ padding: var(--spacing-2) var(--spacing-3);
2616
+ border: var(--border-width-thin) solid var(--color-border);
2617
+ border-radius: var(--radius-md);
2618
+ background: var(--color-surface-base);
2619
+ color: var(--color-text-primary);
2620
+ transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background-color var(--transition-fast);
2621
+ }
2622
+
2623
+ .color-control .color-swatch {
2624
+ position: relative;
2625
+ display: inline-flex;
2626
+ width: calc(var(--spacing-8) + var(--spacing-1));
2627
+ height: calc(var(--spacing-8) + var(--spacing-1));
2628
+ border-radius: var(--radius-sm);
2629
+ }
2630
+
2631
+ .color-control output {
2632
+ margin: 0;
2633
+ min-width: 8ch;
2634
+ font-family: var(--font-family-mono);
2635
+ font-size: var(--font-size-sm);
2636
+ line-height: var(--font-line-height-tight);
2637
+ color: var(--color-text-secondary);
2638
+ text-transform: lowercase;
2639
+ }
2640
+
2641
+ .color-control[data-unset="1"] output {
2642
+ font-style: italic;
2643
+ color: var(--color-text-muted);
2644
+ }
2645
+
2646
+ .color-control input[type="color"] {
2647
+ width: calc(var(--spacing-8) + var(--spacing-1));
2648
+ height: calc(var(--spacing-8) + var(--spacing-1));
2649
+ border-radius: var(--radius-sm);
2650
+ border: var(--border-width-thin) solid var(--color-border);
2651
+ background: transparent;
2652
+ padding: 0;
2653
+ }
2654
+
2655
+ .color-control input[type="color"]::-webkit-color-swatch {
2656
+ border: none;
2657
+ border-radius: calc(var(--radius-sm) - var(--border-width-thin));
2658
+ }
2659
+
2660
+ .color-control input[type="color"]::-moz-color-swatch {
2661
+ border: none;
2662
+ border-radius: calc(var(--radius-sm) - var(--border-width-thin));
2663
+ }
2664
+
2665
+ .color-control .color-swatch[data-unset="1"]::after {
2666
+ content: "";
2667
+ position: absolute;
2668
+ inset: 0;
2669
+ border-radius: var(--radius-sm);
2670
+ border: var(--border-width-thin) solid var(--color-border);
2671
+ background-color: color-mix(in oklab, var(--color-surface-subtle) 78%, var(--color-text-primary) 22%);
2672
+ background-image:
2673
+ linear-gradient(
2674
+ 45deg,
2675
+ color-mix(in oklab, var(--color-surface-base) 88%, var(--color-text-primary) 12%) 25%,
2676
+ transparent 25%,
2677
+ transparent 75%,
2678
+ color-mix(in oklab, var(--color-surface-base) 88%, var(--color-text-primary) 12%) 75%,
2679
+ color-mix(in oklab, var(--color-surface-base) 88%, var(--color-text-primary) 12%)
2680
+ ),
2681
+ linear-gradient(
2682
+ 45deg,
2683
+ color-mix(in oklab, var(--color-surface-base) 88%, var(--color-text-primary) 12%) 25%,
2684
+ transparent 25%,
2685
+ transparent 75%,
2686
+ color-mix(in oklab, var(--color-surface-base) 88%, var(--color-text-primary) 12%) 75%,
2687
+ color-mix(in oklab, var(--color-surface-base) 88%, var(--color-text-primary) 12%)
2688
+ );
2689
+ background-size: calc(var(--spacing-2) * 1.25) calc(var(--spacing-2) * 1.25);
2690
+ background-position:
2691
+ 0 0,
2692
+ calc(var(--spacing-2) * 0.625) calc(var(--spacing-2) * 0.625);
2693
+ pointer-events: none;
2694
+ }
2695
+
2696
+ .color-control .color-swatch[data-unset="1"] input[type="color"] {
2697
+ opacity: 0;
2698
+ }
2699
+
2700
+ &:focus-within .color-control {
2701
+ border-color: var(--color-primary-500);
2702
+ box-shadow: 0 0 0 ${focusWidth}px color-mix(in oklab, var(--color-primary-500) ${Math.round(
2703
+ (focusRingOpacity || 0.3) * 100,
2704
+ )}%, transparent);
2705
+ }
2706
+
2707
+ &:has(input[type="color"]:disabled) .color-control {
2708
+ background: var(--color-input-disabled-bg);
2709
+ color: var(--color-input-disabled-text);
2710
+ cursor: not-allowed;
2711
+ }
2712
+ }
2713
+
2558
2714
  input[type="file"] {
2559
2715
  padding: var(--spacing-2) var(--spacing-4);
2560
2716
  cursor: pointer;
@@ -2762,7 +2918,7 @@ a.btn-working {
2762
2918
  color: var(--color-text-secondary);
2763
2919
  padding: var(--spacing-6) var(--spacing-4);
2764
2920
  background-color: var(--color-surface-subtle);
2765
- max-width: var(--layout-max-width-md);
2921
+ max-width: var(--layout-max-width-md, 736px);
2766
2922
  border-radius: var(--radius-md);
2767
2923
  nav {
2768
2924
  margin-top: var(--spacing-4);
@@ -3126,12 +3282,15 @@ tbody {
3126
3282
  overflow: hidden;
3127
3283
 
3128
3284
  &[open] {
3285
+ overflow: visible;
3286
+
3129
3287
  & > summary::after {
3130
3288
  transform: rotate(45deg);
3131
3289
  }
3132
3290
 
3133
3291
  &::details-content {
3134
3292
  block-size: auto;
3293
+ overflow: visible;
3135
3294
  }
3136
3295
  }
3137
3296
 
@@ -3205,6 +3364,7 @@ tbody {
3205
3364
 
3206
3365
  &[open] > :not(summary) {
3207
3366
  grid-template-rows: 1fr;
3367
+ overflow: visible;
3208
3368
  }
3209
3369
  }
3210
3370
  }
@@ -3508,6 +3668,12 @@ dialog.dialog-full { width: calc(100vw - var(--spacing-8)); max-width: calc(100v
3508
3668
  dialog, dialog::backdrop { transition-duration: 0.01s !important; }
3509
3669
  }
3510
3670
 
3671
+ html:has(dialog[open]:modal) {
3672
+ overflow: hidden;
3673
+ scrollbar-gutter: stable;
3674
+ }
3675
+
3676
+
3511
3677
  `;
3512
3678
  }
3513
3679
 
@@ -3651,9 +3817,8 @@ pds-tabstrip {
3651
3817
  }
3652
3818
 
3653
3819
  #generateIconStyles() {
3654
- const { a11y = {} } = this.options.design;
3655
- const minTouchTarget =
3656
- a11y.minTouchTarget || enums.TouchTargetSizes.standard;
3820
+ const { layout = {} } = this.options.design;
3821
+ const iconOnlySize = layout.buttonMinHeight || 30;
3657
3822
 
3658
3823
  return /*css*/ `/* Icon System */
3659
3824
 
@@ -3700,13 +3865,31 @@ button, a {
3700
3865
 
3701
3866
  &.icon-only {
3702
3867
  padding: var(--spacing-2);
3703
- min-width: ${minTouchTarget}px;
3704
- width: ${minTouchTarget}px;
3705
- height: ${minTouchTarget}px;
3868
+ min-width: ${iconOnlySize}px;
3869
+ width: ${iconOnlySize}px;
3870
+ height: ${iconOnlySize}px;
3706
3871
  display: inline-flex;
3707
3872
  align-items: center;
3708
3873
  justify-content: center;
3709
3874
  }
3875
+
3876
+ &.btn-sm.icon-only {
3877
+ min-width: calc(${iconOnlySize}px * 0.8);
3878
+ width: calc(${iconOnlySize}px * 0.8);
3879
+ height: calc(${iconOnlySize}px * 0.8);
3880
+ }
3881
+
3882
+ &.btn-xs.icon-only {
3883
+ min-width: calc(${iconOnlySize}px * 0.6);
3884
+ width: calc(${iconOnlySize}px * 0.6);
3885
+ height: calc(${iconOnlySize}px * 0.6);
3886
+ }
3887
+
3888
+ &.btn-lg.icon-only {
3889
+ min-width: calc(${iconOnlySize}px * 1.2);
3890
+ width: calc(${iconOnlySize}px * 1.2);
3891
+ height: calc(${iconOnlySize}px * 1.2);
3892
+ }
3710
3893
  }
3711
3894
 
3712
3895
  /* Icon in inputs */
@@ -3757,17 +3940,16 @@ nav[data-dropdown] {
3757
3940
  max-width: none;
3758
3941
  max-inline-size: none;
3759
3942
  opacity: 0;
3760
- scale: 0.95;
3761
3943
  visibility: hidden;
3762
3944
  display: none;
3763
3945
  pointer-events: none;
3764
3946
  transform-origin: top center;
3765
3947
  z-index: var(--z-dropdown, 1050);
3766
3948
  max-height: min(60vh, 24rem);
3949
+ overflow-x: hidden;
3767
3950
  overflow-y: auto;
3768
3951
  transition:
3769
3952
  opacity var(--dropdown-transition-duration) ease,
3770
- scale var(--dropdown-transition-duration) ease,
3771
3953
  visibility 0s linear var(--dropdown-transition-duration),
3772
3954
  display 0s linear var(--dropdown-transition-duration);
3773
3955
  transition-behavior: allow-discrete;
@@ -3776,12 +3958,10 @@ nav[data-dropdown] {
3776
3958
  & > :last-child[aria-hidden="false"] {
3777
3959
  display: inline-block;
3778
3960
  opacity: 1;
3779
- scale: 1;
3780
3961
  visibility: visible;
3781
3962
  pointer-events: auto;
3782
3963
  transition:
3783
3964
  opacity var(--dropdown-transition-duration) ease,
3784
- scale var(--dropdown-transition-duration) ease,
3785
3965
  visibility 0s linear 0s,
3786
3966
  display 0s linear 0s;
3787
3967
  }
@@ -3879,7 +4059,6 @@ nav[data-dropdown] {
3879
4059
  @starting-style {
3880
4060
  nav[data-dropdown] > :last-child[aria-hidden="false"] {
3881
4061
  opacity: 0;
3882
- scale: 0.95;
3883
4062
  }
3884
4063
  }
3885
4064
  `;
@@ -4108,7 +4287,7 @@ nav[data-dropdown] {
4108
4287
  /* Touch device optimizations */
4109
4288
  @media (hover: none) and (pointer: coarse) {
4110
4289
  /* Touch devices - larger touch targets for interactive elements */
4111
- button, a, select, textarea,
4290
+ button:not(.icon-only), a:not(.icon-only), select, textarea,
4112
4291
  input:not([type="radio"]):not([type="checkbox"]) {
4113
4292
  min-height: ${minTouchTarget}px;
4114
4293
  min-width: ${minTouchTarget}px;
@@ -4795,39 +4974,48 @@ ${this.#generateBorderGradientUtilities()}
4795
4974
 
4796
4975
  .surface {
4797
4976
  background-color: var(--color-surface-base);
4977
+ --border-gradient-fill: var(--color-surface-base);
4798
4978
  }
4799
4979
 
4800
4980
  .surface-subtle {
4801
4981
  background-color: var(--color-surface-subtle);
4982
+ --border-gradient-fill: var(--color-surface-subtle);
4802
4983
  }
4803
4984
 
4804
4985
  .surface-elevated {
4805
4986
  background-color: var(--color-surface-elevated);
4987
+ --border-gradient-fill: var(--color-surface-elevated);
4806
4988
  }
4807
4989
 
4808
4990
  .surface-sunken {
4809
4991
  background-color: var(--color-surface-sunken);
4992
+ --border-gradient-fill: var(--color-surface-sunken);
4810
4993
  }
4811
4994
 
4812
4995
  .surface-overlay {
4813
4996
  background-color: var(--color-surface-overlay);
4997
+ --border-gradient-fill: var(--color-surface-overlay);
4814
4998
  }
4815
4999
 
4816
5000
  /* Translucent semantic variants */
4817
5001
  .surface-translucent {
4818
5002
  background-color: var(--color-surface-translucent-50);
5003
+ --border-gradient-fill: var(--color-surface-translucent-50);
4819
5004
  }
4820
5005
 
4821
5006
  .surface-translucent-25 {
4822
5007
  background-color: var(--color-surface-translucent-25);
5008
+ --border-gradient-fill: var(--color-surface-translucent-25);
4823
5009
  }
4824
5010
 
4825
5011
  .surface-translucent-50 {
4826
5012
  background-color: var(--color-surface-translucent-50);
5013
+ --border-gradient-fill: var(--color-surface-translucent-50);
4827
5014
  }
4828
5015
 
4829
5016
  .surface-translucent-75 {
4830
5017
  background-color: var(--color-surface-translucent-75);
5018
+ --border-gradient-fill: var(--color-surface-translucent-75);
4831
5019
  }
4832
5020
 
4833
5021
  /* Legacy utility retained for backwards compatibility (opinionated overlay) */
@@ -4853,6 +5041,7 @@ ${this.#generateBorderGradientUtilities()}
4853
5041
  /* Surface-inverse visual properties (shared, uses smart surface tokens) */
4854
5042
  .surface-inverse {
4855
5043
  background-color: var(--color-surface-inverse);
5044
+ --border-gradient-fill: var(--color-surface-inverse);
4856
5045
  color: var(--surface-inverse-text);
4857
5046
 
4858
5047
  pds-icon {
@@ -4876,7 +5065,7 @@ ${this.#generateBorderGradientUtilities()}
4876
5065
  html:not([data-theme="dark"]) .surface-inverse {
4877
5066
  --color-text-primary: var(--color-gray-100);
4878
5067
  --color-text-secondary: var(--color-gray-300);
4879
- --color-text-muted: var(--color-gray-400);
5068
+ --color-text-muted: var(--color-gray-600);
4880
5069
  --color-border: var(--color-gray-700);
4881
5070
  --color-input-bg: var(--color-gray-800);
4882
5071
  --color-input-disabled-bg: var(--color-gray-900);
@@ -5326,6 +5515,8 @@ export const ${name}CSS = \`${escapedCSS}\`;
5326
5515
  */
5327
5516
  export function validateDesign(designConfig = {}, options = {}) {
5328
5517
  const MIN = Number(options.minContrast || 4.5);
5518
+ const MIN_MUTED = Number(options.minMutedContrast || 3.0);
5519
+ const EXTENDED = Boolean(options.extendedChecks);
5329
5520
 
5330
5521
  // Local helpers (keep public; no dependency on private Generator methods)
5331
5522
  const hexToRgb = (hex) => {
@@ -5366,10 +5557,20 @@ export function validateDesign(designConfig = {}, options = {}) {
5366
5557
  const light = {
5367
5558
  surfaceBg: c.surface?.base,
5368
5559
  surfaceText: c.gray?.[900] || "#000000",
5560
+ surfaceTextSecondary: c.gray?.[700] || c.gray?.[800] || c.gray?.[900],
5561
+ surfaceTextMuted: c.gray?.[500] || c.gray?.[600] || c.gray?.[700],
5562
+ surfaceElevated: c.surface?.elevated || c.surface?.base,
5369
5563
  primaryFill: c.interactive?.light?.fill || c.primary?.[600],
5370
5564
  primaryText: c.interactive?.light?.text || c.primary?.[600],
5565
+ accentFill: c.accent?.[600] || c.accent?.[500],
5566
+ successFill: c.success?.[600] || c.success?.[500],
5567
+ warningFill: c.warning?.[600] || c.warning?.[500],
5568
+ dangerFill: c.danger?.[600] || c.danger?.[500],
5569
+ infoFill: c.info?.[600] || c.info?.[500],
5371
5570
  };
5372
5571
 
5572
+ const bestTextContrast = (bg) => Math.max(contrast(bg, "#ffffff"), contrast(bg, "#000000"));
5573
+
5373
5574
  // Primary button (light): check button fill with white text
5374
5575
  const lightBtnRatio = contrast(light.primaryFill, "#ffffff");
5375
5576
  if (lightBtnRatio < MIN) {
@@ -5398,6 +5599,50 @@ export function validateDesign(designConfig = {}, options = {}) {
5398
5599
  });
5399
5600
  }
5400
5601
 
5602
+ if (EXTENDED) {
5603
+ // Secondary body text (light)
5604
+ const lightSecondaryRatio = contrast(light.surfaceBg, light.surfaceTextSecondary);
5605
+ if (lightSecondaryRatio < MIN) {
5606
+ issues.push({
5607
+ path: "/colors/secondary",
5608
+ message: `Secondary text contrast on base surface (light) is too low (${lightSecondaryRatio.toFixed(
5609
+ 2,
5610
+ )} < ${MIN}).`,
5611
+ ratio: lightSecondaryRatio,
5612
+ min: MIN,
5613
+ context: "light/surface-text-secondary",
5614
+ });
5615
+ }
5616
+
5617
+ // Muted text should still be readable for helper text
5618
+ const lightMutedRatio = contrast(light.surfaceBg, light.surfaceTextMuted);
5619
+ if (lightMutedRatio < MIN_MUTED) {
5620
+ issues.push({
5621
+ path: "/colors/secondary",
5622
+ message: `Muted text contrast on base surface (light) is too low (${lightMutedRatio.toFixed(
5623
+ 2,
5624
+ )} < ${MIN_MUTED}).`,
5625
+ ratio: lightMutedRatio,
5626
+ min: MIN_MUTED,
5627
+ context: "light/surface-text-muted",
5628
+ });
5629
+ }
5630
+
5631
+ // Elevated cards often dominate page UI; enforce readable default text
5632
+ const elevatedTextRatio = contrast(light.surfaceElevated, light.surfaceText);
5633
+ if (elevatedTextRatio < MIN) {
5634
+ issues.push({
5635
+ path: "/colors/background",
5636
+ message: `Elevated surface text contrast (light) is too low (${elevatedTextRatio.toFixed(
5637
+ 2,
5638
+ )} < ${MIN}).`,
5639
+ ratio: elevatedTextRatio,
5640
+ min: MIN,
5641
+ context: "light/surface-elevated-text",
5642
+ });
5643
+ }
5644
+ }
5645
+
5401
5646
  // Primary text for outline/link: check link text on surface
5402
5647
  const lightOutlineRatio = contrast(light.primaryText, light.surfaceBg);
5403
5648
  if (lightOutlineRatio < MIN) {
@@ -5412,11 +5657,39 @@ export function validateDesign(designConfig = {}, options = {}) {
5412
5657
  });
5413
5658
  }
5414
5659
 
5660
+ if (EXTENDED) {
5661
+ // Semantic/accent fills must support readable foreground (white or black)
5662
+ const semanticFills = [
5663
+ { path: "/colors/accent", key: "accent", value: light.accentFill },
5664
+ { path: "/colors/success", key: "success", value: light.successFill },
5665
+ { path: "/colors/warning", key: "warning", value: light.warningFill },
5666
+ { path: "/colors/danger", key: "danger", value: light.dangerFill },
5667
+ { path: "/colors/info", key: "info", value: light.infoFill },
5668
+ ];
5669
+ semanticFills.forEach((entry) => {
5670
+ if (!entry?.value) return;
5671
+ const ratio = bestTextContrast(entry.value);
5672
+ if (ratio < MIN) {
5673
+ issues.push({
5674
+ path: entry.path,
5675
+ message: `${entry.key} fill color cannot achieve accessible text contrast (${ratio.toFixed(
5676
+ 2,
5677
+ )} < ${MIN}) with either white or black text.`,
5678
+ ratio,
5679
+ min: MIN,
5680
+ context: `light/${entry.key}-fill`,
5681
+ });
5682
+ }
5683
+ });
5684
+ }
5685
+
5415
5686
  // Dark theme checks - use computed interactive tokens
5416
5687
  const d = c.dark;
5417
5688
  if (d) {
5418
5689
  const dark = {
5419
5690
  surfaceBg: d.surface?.base || c.surface?.inverse,
5691
+ surfaceText: d.gray?.[50] || d.gray?.[100] || "#ffffff",
5692
+ surfaceTextMuted: d.gray?.[300] || d.gray?.[400] || d.gray?.[500],
5420
5693
  primaryFill: c.interactive?.dark?.fill || d.primary?.[600],
5421
5694
  primaryText: c.interactive?.dark?.text || d.primary?.[600],
5422
5695
  };
@@ -5448,6 +5721,34 @@ export function validateDesign(designConfig = {}, options = {}) {
5448
5721
  context: "dark/outline",
5449
5722
  });
5450
5723
  }
5724
+
5725
+ if (EXTENDED) {
5726
+ const darkTextRatio = contrast(dark.surfaceBg, dark.surfaceText);
5727
+ if (darkTextRatio < MIN) {
5728
+ issues.push({
5729
+ path: "/colors/darkMode/background",
5730
+ message: `Base text contrast on surface (dark) is too low (${darkTextRatio.toFixed(
5731
+ 2,
5732
+ )} < ${MIN}).`,
5733
+ ratio: darkTextRatio,
5734
+ min: MIN,
5735
+ context: "dark/surface-text",
5736
+ });
5737
+ }
5738
+
5739
+ const darkMutedRatio = contrast(dark.surfaceBg, dark.surfaceTextMuted);
5740
+ if (darkMutedRatio < MIN_MUTED) {
5741
+ issues.push({
5742
+ path: "/colors/darkMode/secondary",
5743
+ message: `Muted text contrast on surface (dark) is too low (${darkMutedRatio.toFixed(
5744
+ 2,
5745
+ )} < ${MIN_MUTED}).`,
5746
+ ratio: darkMutedRatio,
5747
+ min: MIN_MUTED,
5748
+ context: "dark/surface-text-muted",
5749
+ });
5750
+ }
5751
+ }
5451
5752
  }
5452
5753
  } catch (err) {
5453
5754
  issues.push({