@pure-ds/core 0.6.9 → 0.6.10

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 (40) hide show
  1. package/custom-elements.json +71 -28
  2. package/dist/types/pds.d.ts +30 -0
  3. package/dist/types/public/assets/js/pds-manager.d.ts +2 -1
  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-edit.d.ts +1 -195
  8. package/dist/types/public/assets/pds/components/pds-live-edit.d.ts.map +1 -1
  9. package/dist/types/public/assets/pds/components/pds-omnibox.d.ts +0 -2
  10. package/dist/types/public/assets/pds/components/pds-omnibox.d.ts.map +1 -1
  11. package/dist/types/src/js/pds-core/pds-config.d.ts +1306 -13
  12. package/dist/types/src/js/pds-core/pds-config.d.ts.map +1 -1
  13. package/dist/types/src/js/pds-core/pds-enhancers-meta.d.ts.map +1 -1
  14. package/dist/types/src/js/pds-core/pds-enhancers.d.ts.map +1 -1
  15. package/dist/types/src/js/pds-core/pds-generator.d.ts.map +1 -1
  16. package/dist/types/src/js/pds-core/pds-live.d.ts.map +1 -1
  17. package/dist/types/src/js/pds-core/pds-ontology.d.ts.map +1 -1
  18. package/dist/types/src/js/pds-core/pds-start-helpers.d.ts +1 -4
  19. package/dist/types/src/js/pds-core/pds-start-helpers.d.ts.map +1 -1
  20. package/dist/types/src/js/pds.d.ts.map +1 -1
  21. package/package.json +2 -2
  22. package/packages/pds-cli/bin/pds-static.js +16 -1
  23. package/public/assets/js/app.js +21 -21
  24. package/public/assets/js/pds-manager.js +291 -161
  25. package/public/assets/js/pds.js +16 -16
  26. package/public/assets/pds/components/pds-form.js +124 -27
  27. package/public/assets/pds/components/pds-live-edit.js +820 -122
  28. package/public/assets/pds/components/pds-omnibox.js +10 -18
  29. package/public/assets/pds/custom-elements.json +71 -28
  30. package/public/assets/pds/pds-css-complete.json +1 -6
  31. package/public/assets/pds/pds.css-data.json +5 -35
  32. package/src/js/pds-core/pds-config.js +822 -31
  33. package/src/js/pds-core/pds-enhancers-meta.js +11 -0
  34. package/src/js/pds-core/pds-enhancers.js +113 -5
  35. package/src/js/pds-core/pds-generator.js +183 -23
  36. package/src/js/pds-core/pds-live.js +177 -2
  37. package/src/js/pds-core/pds-ontology.js +6 -0
  38. package/src/js/pds-core/pds-start-helpers.js +14 -6
  39. package/src/js/pds.d.ts +30 -0
  40. package/src/js/pds.js +36 -60
@@ -55,6 +55,17 @@ export const defaultPDSEnhancerMetadata = [
55
55
  </label>
56
56
  `.trim(),
57
57
  },
58
+ {
59
+ selector: "label[data-color]",
60
+ description:
61
+ "Wraps color inputs with a styled control shell and synced hex output while keeping the native picker.",
62
+ demoHtml: `
63
+ <label data-color>
64
+ <span>Brand color</span>
65
+ <input type="color" value="#7c3aed">
66
+ </label>
67
+ `.trim(),
68
+ },
58
69
  {
59
70
  selector: 'input[type="range"]',
60
71
  description: "Enhances range inputs with an attached <output>.",
@@ -15,6 +15,7 @@ const enhancerDefinitions = [
15
15
  { selector: ".accordion" },
16
16
  { selector: "nav[data-dropdown]" },
17
17
  { selector: "label[data-toggle]" },
18
+ { selector: "label[data-color]" },
18
19
  { selector: 'input[type="range"]' },
19
20
  { selector: "form[data-required]" },
20
21
  { selector: "fieldset[role=group][data-open]" },
@@ -272,9 +273,109 @@ function enhanceToggle(elem) {
272
273
  checkbox.addEventListener("change", updateAria);
273
274
  }
274
275
 
276
+ function enhanceColorInput(elem) {
277
+ if (elem.dataset.enhancedColorInput) return;
278
+
279
+ const input = elem.querySelector('input[type="color"]');
280
+ if (!input) return;
281
+
282
+ elem.dataset.enhancedColorInput = "true";
283
+
284
+ let control = elem.querySelector(':scope > .color-control');
285
+ let swatch = elem.querySelector(':scope > .color-control > .color-swatch');
286
+ let output = elem.querySelector(':scope > .color-control > output');
287
+
288
+ if (!control) {
289
+ control = document.createElement("span");
290
+ control.className = "color-control";
291
+ input.before(control);
292
+ }
293
+
294
+ if (!swatch) {
295
+ swatch = document.createElement("span");
296
+ swatch.className = "color-swatch";
297
+ control.appendChild(swatch);
298
+ }
299
+
300
+ if (input.parentElement !== swatch) {
301
+ swatch.appendChild(input);
302
+ }
303
+
304
+ if (!output) {
305
+ output = document.createElement("output");
306
+ control.appendChild(output);
307
+ }
308
+
309
+ const sync = () => {
310
+ const isUnset = input.dataset.colorUnset === "1";
311
+
312
+ if (isUnset) {
313
+ output.value = "";
314
+ output.textContent = "not set";
315
+ control.dataset.value = "";
316
+ control.dataset.unset = "1";
317
+ swatch.dataset.unset = "1";
318
+ return;
319
+ }
320
+
321
+ output.value = input.value;
322
+ output.textContent = input.value;
323
+ control.dataset.value = input.value;
324
+ delete control.dataset.unset;
325
+ delete swatch.dataset.unset;
326
+ };
327
+
328
+ sync();
329
+
330
+ const setResolved = () => {
331
+ if (input.dataset.colorUnset === "1") {
332
+ input.dataset.colorUnset = "0";
333
+ }
334
+ sync();
335
+ };
336
+
337
+ input.addEventListener("input", setResolved, { passive: true });
338
+ input.addEventListener("change", setResolved, { passive: true });
339
+ }
340
+
275
341
  function enhanceRange(elem) {
276
342
  if (elem.dataset.enhancedRange) return;
277
343
 
344
+ const wireProgrammaticUpdates = (updateFn) => {
345
+ if (elem.dataset.enhancedRangeProgrammatic) return;
346
+ elem.dataset.enhancedRangeProgrammatic = "1";
347
+
348
+ const descriptor =
349
+ Object.getOwnPropertyDescriptor(Object.getPrototypeOf(elem), "value") ||
350
+ Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value");
351
+
352
+ if (descriptor?.get && descriptor?.set) {
353
+ Object.defineProperty(elem, "value", {
354
+ configurable: true,
355
+ enumerable: descriptor.enumerable,
356
+ get() {
357
+ return descriptor.get.call(this);
358
+ },
359
+ set(nextValue) {
360
+ descriptor.set.call(this, nextValue);
361
+ updateFn();
362
+ },
363
+ });
364
+ }
365
+
366
+ const attrObserver = new MutationObserver((mutations) => {
367
+ const shouldUpdate = mutations.some((mutation) => {
368
+ const attr = mutation.attributeName;
369
+ return attr === "value" || attr === "min" || attr === "max";
370
+ });
371
+ if (shouldUpdate) updateFn();
372
+ });
373
+ attrObserver.observe(elem, {
374
+ attributes: true,
375
+ attributeFilter: ["value", "min", "max"],
376
+ });
377
+ };
378
+
278
379
  const label = elem.closest("label");
279
380
  const hasRangeOutputClass = label?.classList.contains("range-output");
280
381
 
@@ -312,6 +413,9 @@ function enhanceRange(elem) {
312
413
  output.textContent = elem.value;
313
414
  };
314
415
  elem.addEventListener("input", updateOutput);
416
+ elem.addEventListener("change", updateOutput);
417
+ wireProgrammaticUpdates(updateOutput);
418
+ updateOutput();
315
419
  }
316
420
  } else {
317
421
  let container = elem.closest(".range-container");
@@ -346,6 +450,8 @@ function enhanceRange(elem) {
346
450
  elem.addEventListener("pointerleave", hide);
347
451
  elem.addEventListener("focus", show);
348
452
  elem.addEventListener("blur", hide);
453
+ elem.addEventListener("change", updateBubble);
454
+ wireProgrammaticUpdates(updateBubble);
349
455
  updateBubble();
350
456
  }
351
457
 
@@ -413,9 +519,9 @@ function enhanceOpenGroup(elem) {
413
519
  addInput.placeholder = "Add item...";
414
520
  addInput.classList.add("input-text", "input-sm");
415
521
  addInput.style.width = "auto";
416
- const firstInput = elem.querySelector(
417
- 'input[type="radio"], input[type="checkbox"]',
418
- );
522
+
523
+ const getFirstInput = () =>
524
+ elem.querySelector('input[type="radio"], input[type="checkbox"]');
419
525
 
420
526
  elem.appendChild(addInput);
421
527
  addInput.addEventListener("keydown", (event) => {
@@ -424,7 +530,8 @@ function enhanceOpenGroup(elem) {
424
530
  if (value) {
425
531
  event.preventDefault();
426
532
 
427
- const type = firstInput.type === "radio" ? "radio" : "checkbox";
533
+ const firstInput = getFirstInput();
534
+ const type = firstInput?.type === "radio" ? "radio" : "checkbox";
428
535
  const id = `open-group-${Math.random().toString(36).substring(2, 11)}`;
429
536
  const label = document.createElement("label");
430
537
 
@@ -435,7 +542,7 @@ function enhanceOpenGroup(elem) {
435
542
  const input = document.createElement("input");
436
543
  input.type = type;
437
544
  input.name =
438
- firstInput.name || elem.getAttribute("data-name") || "open-group";
545
+ firstInput?.name || elem.getAttribute("data-name") || "open-group";
439
546
  input.value = value;
440
547
  input.id = id;
441
548
 
@@ -550,6 +657,7 @@ const enhancerRunners = new Map([
550
657
  [".accordion", enhanceAccordion],
551
658
  ["nav[data-dropdown]", enhanceDropdown],
552
659
  ["label[data-toggle]", enhanceToggle],
660
+ ["label[data-color]", enhanceColorInput],
553
661
  ['input[type="range"]', enhanceRange],
554
662
  ["form[data-required]", enhanceRequired],
555
663
  ["fieldset[role=group][data-open]", enhanceOpenGroup],
@@ -805,7 +805,6 @@ export class Generator {
805
805
 
806
806
  #generateLayoutTokens(layoutConfig) {
807
807
  const {
808
- maxWidth = 1200,
809
808
  containerPadding = 16,
810
809
  breakpoints = {
811
810
  sm: 640,
@@ -815,10 +814,19 @@ export class Generator {
815
814
  },
816
815
  } = layoutConfig;
817
816
 
818
- const resolvedMaxWidths = this.#resolveLayoutMaxWidths(layoutConfig);
817
+ const hasExplicitMaxWidth =
818
+ this.#hasDefinedConfigValue(layoutConfig, "maxWidth");
819
+
820
+ const explicitMaxWidthValue = layoutConfig.maxWidth;
821
+
822
+ const resolvedMaxWidths = this.#resolveLayoutMaxWidths(layoutConfig, {
823
+ emitFallbacks: false,
824
+ });
819
825
 
820
826
  return {
821
- maxWidth: this.#formatLength(maxWidth, "1200px"),
827
+ maxWidth: hasExplicitMaxWidth
828
+ ? this.#formatLength(explicitMaxWidthValue, "1200px")
829
+ : undefined,
822
830
  maxWidthSm: resolvedMaxWidths.sm,
823
831
  maxWidthMd: resolvedMaxWidths.md,
824
832
  maxWidthLg: resolvedMaxWidths.lg,
@@ -841,7 +849,8 @@ export class Generator {
841
849
  };
842
850
  }
843
851
 
844
- #resolveLayoutMaxWidths(layoutConfig = {}) {
852
+ #resolveLayoutMaxWidths(layoutConfig = {}, options = {}) {
853
+ const { emitFallbacks = true } = options;
845
854
  const defaultBreakpoints = {
846
855
  sm: 640,
847
856
  md: 768,
@@ -849,15 +858,29 @@ export class Generator {
849
858
  xl: 1280,
850
859
  };
851
860
 
852
- const {
853
- maxWidths = {},
854
- maxWidth = 1200,
855
- containerPadding = 16,
856
- breakpoints = defaultBreakpoints,
857
- } = layoutConfig || {};
861
+ const { maxWidths = {}, containerPadding = 16, breakpoints = defaultBreakpoints } =
862
+ layoutConfig || {};
863
+
864
+ const hasExplicitMaxWidth =
865
+ this.#hasDefinedConfigValue(layoutConfig, "maxWidth");
866
+
867
+ const hasExplicitMaxWidths = ["sm", "md", "lg", "xl"].some((key) =>
868
+ this.#hasDefinedConfigValue(maxWidths, key),
869
+ );
870
+
871
+ if (!emitFallbacks && !hasExplicitMaxWidth && !hasExplicitMaxWidths) {
872
+ return {
873
+ sm: undefined,
874
+ md: undefined,
875
+ lg: undefined,
876
+ xl: undefined,
877
+ };
878
+ }
879
+
880
+ const maxWidthValue = layoutConfig?.maxWidth;
858
881
 
859
882
  const paddingValue = this.#toNumber(containerPadding, 16);
860
- const baseMaxWidth = this.#toNumber(maxWidth, defaultBreakpoints.xl);
883
+ const baseMaxWidth = this.#toNumber(maxWidthValue, defaultBreakpoints.xl);
861
884
 
862
885
  const resolvedBreakpoints = {
863
886
  sm: this.#toNumber(breakpoints.sm, defaultBreakpoints.sm),
@@ -888,6 +911,15 @@ export class Generator {
888
911
  };
889
912
  }
890
913
 
914
+ #hasDefinedConfigValue(source, key) {
915
+ if (!source || typeof source !== "object") return false;
916
+ if (!Object.prototype.hasOwnProperty.call(source, key)) return false;
917
+ const value = source[key];
918
+ if (value === undefined || value === null) return false;
919
+ if (typeof value === "string" && value.trim().length === 0) return false;
920
+ return true;
921
+ }
922
+
891
923
  #formatLength(value, fallback) {
892
924
  if (typeof value === "number" && Number.isFinite(value)) {
893
925
  return `${value}px`;
@@ -1186,6 +1218,10 @@ export class Generator {
1186
1218
  // Convert camelCase keys to kebab-case
1187
1219
  const kebabKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
1188
1220
 
1221
+ if (value === undefined || value === null) {
1222
+ return;
1223
+ }
1224
+
1189
1225
  // Skip breakpoints object - it's used in JS but doesn't belong in CSS variables
1190
1226
  // Breakpoints are used in @media queries, not as CSS custom properties
1191
1227
  if (key === "breakpoints") {
@@ -1945,7 +1981,7 @@ html[data-theme="dark"] .liquid-glass {
1945
1981
  #generateFormStyles() {
1946
1982
  const {
1947
1983
  shape = {},
1948
- gap,
1984
+ spatialRhythm = {},
1949
1985
  inputPadding,
1950
1986
  buttonPadding,
1951
1987
  focusRingWidth,
@@ -1963,13 +1999,14 @@ html[data-theme="dark"] .liquid-glass {
1963
1999
  ? (enums.BorderWidths[shape.borderWidth] ?? null)
1964
2000
  : null;
1965
2001
 
1966
- const inputPaddingValue = inputPadding || 0.75;
1967
- const buttonPaddingValue = buttonPadding || 1.0;
2002
+ const inputPaddingValue = spatialRhythm.inputPadding ?? inputPadding ?? 0.75;
2003
+ const buttonPaddingValue =
2004
+ spatialRhythm.buttonPadding ?? buttonPadding ?? 1.0;
1968
2005
  const focusWidth = focusRingWidth || 3;
1969
2006
  const borderWidth =
1970
2007
  borderWidthThin || shapeBorderWidth || enums.BorderWidths.thin;
1971
- const gapValue = gap || 1.0;
1972
- const sectionSpacingValue = sectionSpacing || 2.0;
2008
+ const sectionSpacingValue =
2009
+ spatialRhythm.sectionSpacing ?? sectionSpacing ?? 2.0;
1973
2010
  const minButtonHeight = buttonMinHeight || 44;
1974
2011
  const minInputHeight = inputMinHeight || 40;
1975
2012
 
@@ -2235,24 +2272,31 @@ input[type="range"]:active::-moz-range-thumb {
2235
2272
 
2236
2273
  input[type="color"] {
2237
2274
  -webkit-appearance: none;
2275
+ appearance: none;
2238
2276
  padding: 0;
2239
- width: 3rem;
2240
- height: 3rem;
2241
- border-radius: 0.75rem; /* your radius */
2242
- overflow: hidden; /* important */
2277
+ width: calc(var(--spacing-8) + var(--spacing-1));
2278
+ height: calc(var(--spacing-8) + var(--spacing-1));
2279
+ min-height: auto;
2280
+ border-radius: var(--radius-sm);
2281
+ border: var(--border-width-thin) solid var(--color-border);
2282
+ overflow: hidden;
2243
2283
  cursor: pointer;
2284
+ background: transparent;
2244
2285
 
2245
- /* The wrapper */
2246
2286
  &::-webkit-color-swatch-wrapper {
2247
2287
  padding: 0;
2248
2288
  border-radius: inherit;
2249
2289
  }
2250
2290
 
2251
- /* The swatch (the actual color box) */
2252
2291
  &::-webkit-color-swatch {
2253
2292
  border: none;
2254
2293
  border-radius: inherit;
2255
2294
  }
2295
+
2296
+ &::-moz-color-swatch {
2297
+ border: none;
2298
+ border-radius: inherit;
2299
+ }
2256
2300
  }
2257
2301
 
2258
2302
  /* Button-style checkbox inputs outside of fieldsets */
@@ -2555,6 +2599,116 @@ label[data-toggle] {
2555
2599
  }
2556
2600
  }
2557
2601
 
2602
+ /* Color input enhancement shell - applied by enhanceColorInput on label[data-color] */
2603
+ label[data-color] {
2604
+ display: grid;
2605
+ gap: var(--spacing-2);
2606
+
2607
+ .color-control {
2608
+ display: inline-flex;
2609
+ align-items: center;
2610
+ gap: var(--spacing-3);
2611
+ width: fit-content;
2612
+ min-height: var(--input-min-height, 40px);
2613
+ padding: var(--spacing-2) var(--spacing-3);
2614
+ border: var(--border-width-thin) solid var(--color-border);
2615
+ border-radius: var(--radius-md);
2616
+ background: var(--color-surface-base);
2617
+ color: var(--color-text-primary);
2618
+ transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background-color var(--transition-fast);
2619
+ }
2620
+
2621
+ .color-control .color-swatch {
2622
+ position: relative;
2623
+ display: inline-flex;
2624
+ width: calc(var(--spacing-8) + var(--spacing-1));
2625
+ height: calc(var(--spacing-8) + var(--spacing-1));
2626
+ border-radius: var(--radius-sm);
2627
+ }
2628
+
2629
+ .color-control output {
2630
+ margin: 0;
2631
+ min-width: 8ch;
2632
+ font-family: var(--font-family-mono);
2633
+ font-size: var(--font-size-sm);
2634
+ line-height: var(--font-line-height-tight);
2635
+ color: var(--color-text-secondary);
2636
+ text-transform: lowercase;
2637
+ }
2638
+
2639
+ .color-control[data-unset="1"] output {
2640
+ font-style: italic;
2641
+ color: var(--color-text-muted);
2642
+ }
2643
+
2644
+ .color-control input[type="color"] {
2645
+ width: calc(var(--spacing-8) + var(--spacing-1));
2646
+ height: calc(var(--spacing-8) + var(--spacing-1));
2647
+ border-radius: var(--radius-sm);
2648
+ border: var(--border-width-thin) solid var(--color-border);
2649
+ background: transparent;
2650
+ padding: 0;
2651
+ }
2652
+
2653
+ .color-control input[type="color"]::-webkit-color-swatch {
2654
+ border: none;
2655
+ border-radius: calc(var(--radius-sm) - var(--border-width-thin));
2656
+ }
2657
+
2658
+ .color-control input[type="color"]::-moz-color-swatch {
2659
+ border: none;
2660
+ border-radius: calc(var(--radius-sm) - var(--border-width-thin));
2661
+ }
2662
+
2663
+ .color-control .color-swatch[data-unset="1"]::after {
2664
+ content: "";
2665
+ position: absolute;
2666
+ inset: 0;
2667
+ border-radius: var(--radius-sm);
2668
+ border: var(--border-width-thin) solid var(--color-border);
2669
+ background-color: color-mix(in oklab, var(--color-surface-subtle) 78%, var(--color-text-primary) 22%);
2670
+ background-image:
2671
+ linear-gradient(
2672
+ 45deg,
2673
+ color-mix(in oklab, var(--color-surface-base) 88%, var(--color-text-primary) 12%) 25%,
2674
+ transparent 25%,
2675
+ transparent 75%,
2676
+ color-mix(in oklab, var(--color-surface-base) 88%, var(--color-text-primary) 12%) 75%,
2677
+ color-mix(in oklab, var(--color-surface-base) 88%, var(--color-text-primary) 12%)
2678
+ ),
2679
+ linear-gradient(
2680
+ 45deg,
2681
+ color-mix(in oklab, var(--color-surface-base) 88%, var(--color-text-primary) 12%) 25%,
2682
+ transparent 25%,
2683
+ transparent 75%,
2684
+ color-mix(in oklab, var(--color-surface-base) 88%, var(--color-text-primary) 12%) 75%,
2685
+ color-mix(in oklab, var(--color-surface-base) 88%, var(--color-text-primary) 12%)
2686
+ );
2687
+ background-size: calc(var(--spacing-2) * 1.25) calc(var(--spacing-2) * 1.25);
2688
+ background-position:
2689
+ 0 0,
2690
+ calc(var(--spacing-2) * 0.625) calc(var(--spacing-2) * 0.625);
2691
+ pointer-events: none;
2692
+ }
2693
+
2694
+ .color-control .color-swatch[data-unset="1"] input[type="color"] {
2695
+ opacity: 0;
2696
+ }
2697
+
2698
+ &:focus-within .color-control {
2699
+ border-color: var(--color-primary-500);
2700
+ box-shadow: 0 0 0 ${focusWidth}px color-mix(in oklab, var(--color-primary-500) ${Math.round(
2701
+ (focusRingOpacity || 0.3) * 100,
2702
+ )}%, transparent);
2703
+ }
2704
+
2705
+ &:has(input[type="color"]:disabled) .color-control {
2706
+ background: var(--color-input-disabled-bg);
2707
+ color: var(--color-input-disabled-text);
2708
+ cursor: not-allowed;
2709
+ }
2710
+ }
2711
+
2558
2712
  input[type="file"] {
2559
2713
  padding: var(--spacing-2) var(--spacing-4);
2560
2714
  cursor: pointer;
@@ -2762,7 +2916,7 @@ a.btn-working {
2762
2916
  color: var(--color-text-secondary);
2763
2917
  padding: var(--spacing-6) var(--spacing-4);
2764
2918
  background-color: var(--color-surface-subtle);
2765
- max-width: var(--layout-max-width-md);
2919
+ max-width: var(--layout-max-width-md, 736px);
2766
2920
  border-radius: var(--radius-md);
2767
2921
  nav {
2768
2922
  margin-top: var(--spacing-4);
@@ -3508,6 +3662,12 @@ dialog.dialog-full { width: calc(100vw - var(--spacing-8)); max-width: calc(100v
3508
3662
  dialog, dialog::backdrop { transition-duration: 0.01s !important; }
3509
3663
  }
3510
3664
 
3665
+ html:has(dialog[open]:modal) {
3666
+ overflow: hidden;
3667
+ scrollbar-gutter: stable;
3668
+ }
3669
+
3670
+
3511
3671
  `;
3512
3672
  }
3513
3673