@pure-ds/storybook 0.4.19 → 0.4.21

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.
@@ -146,6 +146,57 @@ function serializeForDisplay(value) {
146
146
  }
147
147
  }
148
148
 
149
+ /**
150
+ * Generate realistic source code for pds-form elements
151
+ */
152
+ function generatePdsFormMarkup(formElement) {
153
+ const attrs = [];
154
+
155
+ // Check for properties
156
+ if (formElement.jsonSchema) {
157
+ attrs.push('.jsonSchema=${schema}');
158
+ }
159
+ if (formElement.uiSchema) {
160
+ attrs.push('.uiSchema=${uiSchema}');
161
+ }
162
+ if (formElement.values) {
163
+ attrs.push('.values=${values}');
164
+ }
165
+ if (formElement.options && Object.keys(formElement.options).length > 0) {
166
+ attrs.push('.options=${options}');
167
+ }
168
+
169
+ // Check for common event listeners by looking at the element's event listeners
170
+ // Since we can't easily detect event listeners, we'll check common patterns
171
+ const hasSubmitHandler = formElement.getAttribute('data-has-submit') !== null || true; // Assume submit handler
172
+ if (hasSubmitHandler) {
173
+ attrs.push('@pw:submit=${(e) => handleSubmit(e.detail)}');
174
+ }
175
+
176
+ // Check for boolean attributes
177
+ if (formElement.hasAttribute('data-required')) {
178
+ attrs.push('data-required');
179
+ }
180
+ if (formElement.hasAttribute('hide-actions')) {
181
+ attrs.push('hide-actions');
182
+ }
183
+
184
+ const formattedAttrs = attrs.length > 0
185
+ ? '\n ' + attrs.join('\n ') + '\n'
186
+ : '';
187
+
188
+ // Check for slotted content
189
+ const slots = formElement.querySelectorAll('[slot]');
190
+ let slotContent = '';
191
+ if (slots.length > 0) {
192
+ slotContent = '\n ' + Array.from(slots).map(slot => {
193
+ return `<div slot="${slot.getAttribute('slot')}">\n <!-- slotted content -->\n </div>`;
194
+ }).join('\n ') + '\n';
195
+ }
196
+
197
+ return `<pds-form${formattedAttrs}>${slotContent}</pds-form>`;
198
+ }
199
+
149
200
  /**
150
201
  * Global decorator that extracts and sends HTML to the panel
151
202
  */
@@ -160,18 +211,44 @@ export const withHTMLExtractor = (storyFn, context) => {
160
211
  // Try to get HTML from the story container
161
212
  const container = document.querySelector('#storybook-root');
162
213
  if (container) {
163
- if (story && story._$litType$) {
164
- html = await litToHTML(story);
214
+ // Check if this story has pds-form elements
215
+ const pdsFormElements = Array.from(container.querySelectorAll('pds-form'));
216
+
217
+ if (pdsFormElements.length > 0) {
218
+ // Generate realistic markup for pds-form stories
219
+ const alerts = Array.from(container.querySelectorAll('.alert'));
220
+ let markup = '';
221
+
222
+ // Include any alert/info boxes before the form
223
+ if (alerts.length > 0) {
224
+ alerts.forEach(alert => {
225
+ markup += `<div class="alert ${alert.className.split(' ').filter(c => c !== 'alert').join(' ')}">\n`;
226
+ markup += ` ${alert.innerHTML.replace(/\n\s+/g, '\n ')}\n`;
227
+ markup += `</div>\n\n`;
228
+ });
229
+ }
230
+
231
+ // Add pds-form markup
232
+ pdsFormElements.forEach(form => {
233
+ markup += generatePdsFormMarkup(form);
234
+ });
235
+
236
+ html = markup;
165
237
  } else {
166
- html = formatHTML(extractHTML(container));
238
+ // No pds-form elements, use standard extraction
239
+ if (story && story._$litType$) {
240
+ html = await litToHTML(story);
241
+ } else {
242
+ html = formatHTML(extractHTML(container));
243
+ }
167
244
  }
168
245
 
169
- const forms = Array.from(container.querySelectorAll('pds-form'))
246
+ const forms = pdsFormElements
170
247
  .map((form, index) => {
171
248
  const label =
172
249
  form.getAttribute?.('id') ||
173
250
  form.getAttribute?.('name') ||
174
- (Array.from(container.querySelectorAll('pds-form')).length > 1 ? `Form ${index + 1}` : 'Form');
251
+ (pdsFormElements.length > 1 ? `Form ${index + 1}` : 'Form');
175
252
 
176
253
  const jsonSchema = serializeForDisplay(form.jsonSchema);
177
254
  const uiSchema = serializeForDisplay(form.uiSchema);
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-01-13T12:40:57.809Z",
2
+ "generatedAt": "2026-01-14T14:26:09.141Z",
3
3
  "sources": {
4
4
  "customElements": "custom-elements.json",
5
5
  "ontology": "src\\js\\pds-core\\pds-ontology.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pure-ds/storybook",
3
- "version": "0.4.19",
3
+ "version": "0.4.21",
4
4
  "description": "Storybook showcase for Pure Design System with live configuration",
5
5
  "type": "module",
6
6
  "private": false,
@@ -37,7 +37,7 @@
37
37
  "pds:build-icons": "pds-build-icons"
38
38
  },
39
39
  "peerDependencies": {
40
- "@pure-ds/core": "^0.4.19"
40
+ "@pure-ds/core": "^0.4.21"
41
41
  },
42
42
  "dependencies": {
43
43
  "@custom-elements-manifest/analyzer": "^0.11.0",
@@ -645,6 +645,10 @@ fieldset {
645
645
  color: var(--color-primary-700);
646
646
  }
647
647
  }
648
+
649
+ input[type="checkbox"]{
650
+ border-radius: var(--radius-xs);
651
+ }
648
652
 
649
653
  input[type="radio"],
650
654
  input[type="checkbox"] {
@@ -653,18 +657,27 @@ fieldset {
653
657
  width: var(--spacing-5);
654
658
  height: var(--spacing-5);
655
659
  min-height: var(--spacing-5);
660
+ padding: var(--spacing-2);
656
661
  margin: 0;
657
662
  cursor: pointer;
658
663
  flex-shrink: 0;
659
664
  accent-color: var(--color-primary-600);
660
- appearance: auto;
661
- -webkit-appearance: auto;
662
- -moz-appearance: auto;
663
-
664
- &:focus {
665
- outline: 2px solid var(--color-primary-500);
666
- outline-offset: 2px;
665
+
666
+ &:focus-visible {
667
+ outline: none;
668
+
669
+ box-shadow:
670
+ 0 0 0 2px var(--color-primary-500),
671
+ 0 0 0 4px color-mix(in srgb,
672
+ var(--color-primary-500) 40%,
673
+ transparent
674
+ );
667
675
  }
676
+
677
+ &:checked {
678
+ background-color: var(--color-primary-600);
679
+ }
680
+
668
681
  }
669
682
  }
670
683
 
@@ -643,6 +643,10 @@ fieldset {
643
643
  color: var(--color-primary-700);
644
644
  }
645
645
  }
646
+
647
+ input[type="checkbox"]{
648
+ border-radius: var(--radius-xs);
649
+ }
646
650
 
647
651
  input[type="radio"],
648
652
  input[type="checkbox"] {
@@ -651,18 +655,27 @@ fieldset {
651
655
  width: var(--spacing-5);
652
656
  height: var(--spacing-5);
653
657
  min-height: var(--spacing-5);
658
+ padding: var(--spacing-2);
654
659
  margin: 0;
655
660
  cursor: pointer;
656
661
  flex-shrink: 0;
657
662
  accent-color: var(--color-primary-600);
658
- appearance: auto;
659
- -webkit-appearance: auto;
660
- -moz-appearance: auto;
661
-
662
- &:focus {
663
- outline: 2px solid var(--color-primary-500);
664
- outline-offset: 2px;
663
+
664
+ &:focus-visible {
665
+ outline: none;
666
+
667
+ box-shadow:
668
+ 0 0 0 2px var(--color-primary-500),
669
+ 0 0 0 4px color-mix(in srgb,
670
+ var(--color-primary-500) 40%,
671
+ transparent
672
+ );
665
673
  }
674
+
675
+ &:checked {
676
+ background-color: var(--color-primary-600);
677
+ }
678
+
666
679
  }
667
680
  }
668
681
 
@@ -1116,6 +1116,7 @@ export class SchemaForm extends LitElement {
1116
1116
  this.#deleteByPathPrefix(this.#data, path + "/");
1117
1117
  this.requestUpdate();
1118
1118
  this.#emit("pw:value-change", {
1119
+ path,
1119
1120
  name: path,
1120
1121
  value: i,
1121
1122
  validity: { valid: true },
@@ -1865,7 +1866,6 @@ export class SchemaForm extends LitElement {
1865
1866
  <select
1866
1867
  id=${id}
1867
1868
  name=${path}
1868
- .value=${value ?? ""}
1869
1869
  ?disabled=${!!attrs.disabled}
1870
1870
  ?required=${!!attrs.required}
1871
1871
  ?data-dropdown=${useDropdown}
@@ -1874,7 +1874,7 @@ export class SchemaForm extends LitElement {
1874
1874
  <option value="" ?selected=${value == null}>—</option>
1875
1875
  ${enumValues.map(
1876
1876
  (v, i) =>
1877
- html`<option value=${String(v)}>
1877
+ html`<option value=${String(v)} ?selected=${String(value) === String(v)}>
1878
1878
  ${String(enumLabels[i])}
1879
1879
  </option>`
1880
1880
  )}
@@ -2092,17 +2092,28 @@ export class SchemaForm extends LitElement {
2092
2092
  const withoutSlash = path.startsWith("/") ? path.substring(1) : path;
2093
2093
  if (this.uiSchema[withoutSlash]) return this.uiSchema[withoutSlash];
2094
2094
 
2095
- // Try nested navigation (e.g., userProfile/settings/preferences/theme)
2095
+ // Try nested navigation (e.g., /accountType/companyName)
2096
2096
  // Skip array indices (numeric parts and wildcard *) when navigating UI schema
2097
2097
  const parts = path.replace(/^\//, "").split("/");
2098
2098
  let current = this.uiSchema;
2099
- for (const part of parts) {
2099
+ for (let i = 0; i < parts.length; i++) {
2100
+ const part = parts[i];
2100
2101
  // Skip numeric array indices and wildcard in UI schema navigation
2101
2102
  if (/^\d+$/.test(part) || part === "*") continue;
2102
2103
 
2104
+ // Try both with and without leading slash for each segment
2103
2105
  if (current && typeof current === "object" && part in current) {
2104
2106
  current = current[part];
2107
+ } else if (current && typeof current === "object" && ("/" + part) in current) {
2108
+ current = current["/" + part];
2105
2109
  } else {
2110
+ // If this is the first segment, try looking up the full path from root
2111
+ if (i === 0) {
2112
+ const fullPath = "/" + parts.join("/");
2113
+ if (this.uiSchema[fullPath]) {
2114
+ return this.uiSchema[fullPath];
2115
+ }
2116
+ }
2106
2117
  return undefined;
2107
2118
  }
2108
2119
  }
@@ -2227,17 +2238,23 @@ export class SchemaForm extends LitElement {
2227
2238
  this.#setByPath(this.#data, path, val);
2228
2239
 
2229
2240
  // Apply calculated values for any dependent fields
2230
- this.#applyCalculatedValues(path);
2241
+ // This will call requestUpdate() if there are dependents
2242
+ const hadDependents = this.#applyCalculatedValues(path);
2243
+
2244
+ // Only request update here if there were no dependents
2245
+ // (to avoid double render)
2246
+ if (!hadDependents) {
2247
+ this.requestUpdate();
2248
+ }
2231
2249
 
2232
- this.requestUpdate();
2233
2250
  const validity = { valid: true };
2234
- this.#emit("pw:value-change", { name: path, value: val, validity });
2251
+ this.#emit("pw:value-change", { path, name: path, value: val, validity });
2235
2252
  }
2236
2253
 
2237
2254
  #applyCalculatedValues(changedPath) {
2238
2255
  // Find fields that depend on the changed path
2239
2256
  const dependents = this.#dependencies.get(changedPath);
2240
- if (!dependents) return;
2257
+ if (!dependents || dependents.size === 0) return false;
2241
2258
 
2242
2259
  for (const targetPath of dependents) {
2243
2260
  const ui = this.#uiFor(targetPath);
@@ -2255,6 +2272,11 @@ export class SchemaForm extends LitElement {
2255
2272
  }
2256
2273
  }
2257
2274
  }
2275
+
2276
+ // Force re-render since there were dependents
2277
+ // This ensures ui:visibleWhen, ui:requiredWhen, ui:disabledWhen get re-evaluated
2278
+ this.requestUpdate();
2279
+ return true; // Indicate that we triggered a re-render
2258
2280
  }
2259
2281
 
2260
2282
  #getByPath(obj, path) {
@@ -1838,6 +1838,10 @@ fieldset {
1838
1838
  color: var(--color-primary-700);
1839
1839
  }
1840
1840
  }
1841
+
1842
+ input[type="checkbox"]{
1843
+ border-radius: var(--radius-xs);
1844
+ }
1841
1845
 
1842
1846
  input[type="radio"],
1843
1847
  input[type="checkbox"] {
@@ -1846,18 +1850,27 @@ fieldset {
1846
1850
  width: var(--spacing-5);
1847
1851
  height: var(--spacing-5);
1848
1852
  min-height: var(--spacing-5);
1853
+ padding: var(--spacing-2);
1849
1854
  margin: 0;
1850
1855
  cursor: pointer;
1851
1856
  flex-shrink: 0;
1852
1857
  accent-color: var(--color-primary-600);
1853
- appearance: auto;
1854
- -webkit-appearance: auto;
1855
- -moz-appearance: auto;
1856
-
1857
- &:focus {
1858
- outline: 2px solid var(--color-primary-500);
1859
- outline-offset: 2px;
1858
+
1859
+ &:focus-visible {
1860
+ outline: none;
1861
+
1862
+ box-shadow:
1863
+ 0 0 0 2px var(--color-primary-500),
1864
+ 0 0 0 4px color-mix(in srgb,
1865
+ var(--color-primary-500) 40%,
1866
+ transparent
1867
+ );
1860
1868
  }
1869
+
1870
+ &:checked {
1871
+ background-color: var(--color-primary-600);
1872
+ }
1873
+
1861
1874
  }
1862
1875
  }
1863
1876
 
@@ -1020,6 +1020,151 @@ export const WithGridLayout = {
1020
1020
  },
1021
1021
  };
1022
1022
 
1023
+ export const WithGridLayoutAuto = {
1024
+ name: "Grid Layout - Auto",
1025
+ parameters: {
1026
+ docs: {
1027
+ description: {
1028
+ story: `Auto-fit grid layout that automatically adjusts the number of columns based on available space and minimum column width.
1029
+
1030
+ This is the **PDS way** to create responsive grids without media queries. Use \`columns: "auto"\` with \`autoSize\` to specify the minimum column width.
1031
+
1032
+ ### Usage Example
1033
+
1034
+ \`\`\`html
1035
+ <pds-form
1036
+ .jsonSchema=\${schema}
1037
+ .uiSchema=\${uiSchema}
1038
+ .options=\${options}
1039
+ @pw:submit=\${(e) => toastFormData(e.detail)}
1040
+ ></pds-form>
1041
+
1042
+ <script type="module">
1043
+ import { html } from 'lit';
1044
+
1045
+ const schema = {
1046
+ type: "object",
1047
+ properties: {
1048
+ productInfo: {
1049
+ type: "object",
1050
+ title: "Product Information",
1051
+ properties: {
1052
+ name: { type: "string", title: "Product Name" },
1053
+ sku: { type: "string", title: "SKU" },
1054
+ price: { type: "number", title: "Price" },
1055
+ // ... more fields
1056
+ }
1057
+ }
1058
+ }
1059
+ };
1060
+
1061
+ const uiSchema = {
1062
+ productInfo: {
1063
+ "ui:layout": "grid",
1064
+ "ui:layoutOptions": {
1065
+ columns: "auto", // Auto-fit columns
1066
+ autoSize: "md", // Min width ~200px (grid-auto-md)
1067
+ gap: "md"
1068
+ }
1069
+ }
1070
+ };
1071
+
1072
+ const options = {
1073
+ widgets: { booleans: "toggle" }
1074
+ };
1075
+ </script>
1076
+ \`\`\`
1077
+
1078
+ ### Available sizes:
1079
+ - \`"sm"\` → ~150px minimum width
1080
+ - \`"md"\` → ~200px minimum width
1081
+ - \`"lg"\` → ~250px minimum width
1082
+ - \`"xl"\` → ~300px minimum width
1083
+
1084
+ The grid automatically creates as many columns as will fit, wrapping to new rows when needed. **No JavaScript required!**`,
1085
+ },
1086
+ },
1087
+ },
1088
+ render: () => {
1089
+ const schema = {
1090
+ type: "object",
1091
+ properties: {
1092
+ productInfo: {
1093
+ type: "object",
1094
+ title: "Product Information",
1095
+ properties: {
1096
+ name: {
1097
+ type: "string",
1098
+ title: "Product Name",
1099
+ examples: ["Wireless Headphones"],
1100
+ },
1101
+ sku: { type: "string", title: "SKU", examples: ["WH-1000XM4"] },
1102
+ price: { type: "number", title: "Price", examples: [299.99] },
1103
+ quantity: { type: "integer", title: "Quantity", examples: [50] },
1104
+ category: {
1105
+ type: "string",
1106
+ title: "Category",
1107
+ enum: [
1108
+ "Electronics",
1109
+ "Clothing",
1110
+ "Books",
1111
+ "Home",
1112
+ "Sports",
1113
+ "Garden",
1114
+ ],
1115
+ },
1116
+ brand: { type: "string", title: "Brand", examples: ["Sony"] },
1117
+ weight: { type: "number", title: "Weight (kg)", examples: [0.25] },
1118
+ dimensions: {
1119
+ type: "string",
1120
+ title: "Dimensions",
1121
+ examples: ["20 x 18 x 8 cm"],
1122
+ },
1123
+ inStock: {
1124
+ type: "boolean",
1125
+ title: "In Stock",
1126
+ default: true,
1127
+ },
1128
+ featured: {
1129
+ type: "boolean",
1130
+ title: "Featured Product",
1131
+ default: false,
1132
+ },
1133
+ },
1134
+ },
1135
+ },
1136
+ };
1137
+
1138
+ const uiSchema = {
1139
+ productInfo: {
1140
+ "ui:layout": "grid",
1141
+ "ui:layoutOptions": {
1142
+ columns: "auto", // Auto-fit columns
1143
+ autoSize: "md", // Min column width (grid-auto-md)
1144
+ gap: "md"
1145
+ },
1146
+ },
1147
+ };
1148
+
1149
+ const options = {
1150
+ widgets: { booleans: "toggle" },
1151
+ };
1152
+
1153
+ return html`
1154
+ <div class="alert alert-info">
1155
+ <p><strong>💡 Try resizing your browser window!</strong></p>
1156
+ <p>The grid automatically adjusts the number of columns based on available space. Fields maintain a minimum width of ~200px and wrap to new rows as needed.</p>
1157
+ </div>
1158
+ <pds-form
1159
+ .jsonSchema=${schema}
1160
+ .uiSchema=${uiSchema}
1161
+ .options=${options}
1162
+ @pw:submit=${(e) => toastFormData(e.detail)}
1163
+ ></pds-form>
1164
+ `;
1165
+ },
1166
+ };
1167
+
1023
1168
  export const WithAccordionLayout = {
1024
1169
  name: "Accordion Layout",
1025
1170
  render: () => {
@@ -2698,6 +2843,136 @@ const uiSchema = {
2698
2843
  },
2699
2844
  };
2700
2845
 
2846
+ export const OneOfWithPresetValues = {
2847
+ parameters: {
2848
+ docs: {
2849
+ description: {
2850
+ story: `Demonstrates that \`oneOf\` and \`anyOf\` select fields correctly display preset values.
2851
+
2852
+ This story showcases the fix for a bug where select dropdowns wouldn't show the selected value when using \`.values\` to preset form data.
2853
+
2854
+ ### Example Schema:
2855
+ \`\`\`javascript
2856
+ {
2857
+ organizationType: {
2858
+ oneOf: [
2859
+ { const: "ngo", title: "Non-Governmental Organization" },
2860
+ { const: "charity", title: "Charitable Organization" },
2861
+ { const: "company", title: "Commercial Company" }
2862
+ ]
2863
+ }
2864
+ }
2865
+ \`\`\`
2866
+
2867
+ ### Preset Values:
2868
+ \`\`\`javascript
2869
+ <pds-form
2870
+ .values=$\{{ organizationType: "charity", category: "health" }}
2871
+ ...
2872
+ >
2873
+ \`\`\`
2874
+
2875
+ The dropdowns should correctly show "Charitable Organization" and "Health & Medical" as selected.`,
2876
+ },
2877
+ source: {
2878
+ code: `<pds-form
2879
+ data-required
2880
+ .jsonSchema=\${schema}
2881
+ .uiSchema=\${uiSchema}
2882
+ .values=\${initialValues}
2883
+ @pw:submit=\${(e) => toastFormData(e.detail)}
2884
+ ></pds-form>`,
2885
+ },
2886
+ },
2887
+ },
2888
+ render: () => {
2889
+ const schema = {
2890
+ type: "object",
2891
+ title: "Organization Registration",
2892
+ properties: {
2893
+ organizationName: {
2894
+ type: "string",
2895
+ title: "Organization Name",
2896
+ examples: ["Red Cross International"],
2897
+ },
2898
+ organizationType: {
2899
+ type: "string",
2900
+ title: "Organization Type",
2901
+ oneOf: [
2902
+ { const: "ngo", title: "Non-Governmental Organization" },
2903
+ { const: "charity", title: "Charitable Organization" },
2904
+ { const: "company", title: "Commercial Company" },
2905
+ { const: "government", title: "Government Agency" },
2906
+ { const: "educational", title: "Educational Institution" },
2907
+ ],
2908
+ },
2909
+ category: {
2910
+ type: "string",
2911
+ title: "Primary Category",
2912
+ anyOf: [
2913
+ { const: "health", title: "Health & Medical" },
2914
+ { const: "education", title: "Education & Research" },
2915
+ { const: "environment", title: "Environment & Conservation" },
2916
+ { const: "social", title: "Social Services" },
2917
+ { const: "arts", title: "Arts & Culture" },
2918
+ { const: "technology", title: "Technology & Innovation" },
2919
+ ],
2920
+ },
2921
+ registrationNumber: {
2922
+ type: "string",
2923
+ title: "Registration Number",
2924
+ examples: ["123-456-789"],
2925
+ },
2926
+ country: {
2927
+ type: "string",
2928
+ title: "Country",
2929
+ enum: ["NL", "BE", "DE", "FR", "GB"],
2930
+ },
2931
+ },
2932
+ required: ["organizationName", "organizationType", "category"],
2933
+ };
2934
+
2935
+ const uiSchema = {
2936
+ "ui:layout": "grid",
2937
+ "ui:layoutOptions": {
2938
+ columns: "auto", // Auto-fit columns
2939
+ autoSize: "md", // Min column width (grid-auto-md)
2940
+ gap: "md"
2941
+ }
2942
+ };
2943
+
2944
+ // Preset values that should be correctly selected in the dropdowns
2945
+ const initialValues = {
2946
+ organizationName: "Global Health Foundation",
2947
+ organizationType: "charity",
2948
+ category: "health",
2949
+ registrationNumber: "974983702",
2950
+ country: "NL",
2951
+ };
2952
+
2953
+ return html`
2954
+ <div class="alert alert-info">
2955
+ <p>
2956
+ <strong>✓ This form has preset values:</strong> The dropdowns should
2957
+ correctly show "Charitable Organization", "Health & Medical", and
2958
+ "NL" as selected.
2959
+ </p>
2960
+ <p>
2961
+ Try changing the values and submitting to see how oneOf/anyOf/enum
2962
+ work together.
2963
+ </p>
2964
+ </div>
2965
+ <pds-form
2966
+ data-required
2967
+ .jsonSchema=${schema}
2968
+ .uiSchema=${uiSchema}
2969
+ .values=${initialValues}
2970
+ @pw:submit=${(e) => toastFormData(e.detail)}
2971
+ ></pds-form>
2972
+ `;
2973
+ },
2974
+ };
2975
+
2701
2976
  export const ConditionalRequired = {
2702
2977
  parameters: {
2703
2978
  docs: {
@@ -2770,13 +3045,16 @@ export const ConditionalComplex = {
2770
3045
  parameters: {
2771
3046
  docs: {
2772
3047
  description: {
2773
- story: `A comprehensive example combining multiple conditional features: visibility, required states, and calculated values.
3048
+ story: `A comprehensive example combining multiple conditional features with accordion layout to reduce visual complexity.
2774
3049
 
2775
3050
  ### Features demonstrated:
2776
- - **Show/Hide**: Company name appears for business accounts; shipping address hidden for pickup
3051
+ - **Accordion Sections**: Related fields grouped into collapsible sections
3052
+ - **Show/Hide**: Business account section appears only for business accounts; shipping address hidden for pickup
2777
3053
  - **Conditional Required**: Company name required for business; phone required when preferred
2778
3054
  - **Disable**: Email disabled when phone is preferred
2779
- - **Calculations**: Full name, subtotal, shipping cost, and total are all computed automatically`,
3055
+ - **Calculations**: Full name, subtotal, shipping cost, and total are all computed automatically
3056
+
3057
+ The accordion layout prevents awkward field jumping between columns and makes the form easier to navigate.`,
2780
3058
  },
2781
3059
  },
2782
3060
  },
@@ -2786,143 +3064,191 @@ export const ConditionalComplex = {
2786
3064
  title: "Order Form",
2787
3065
  properties: {
2788
3066
  accountType: {
2789
- type: "string",
3067
+ type: "object",
2790
3068
  title: "Account Type",
2791
- oneOf: [
2792
- { const: "personal", title: "Personal" },
2793
- { const: "business", title: "Business" },
2794
- ],
2795
- default: "personal",
2796
- },
2797
- companyName: {
2798
- type: "string",
2799
- title: "Company Name",
2800
- examples: ["Acme Inc."],
2801
- },
2802
- preferPhone: {
2803
- type: "boolean",
2804
- title: "I prefer to be contacted by phone",
2805
- default: false,
2806
- },
2807
- email: {
2808
- type: "string",
2809
- format: "email",
2810
- title: "Email",
2811
- examples: ["you@example.com"],
2812
- },
2813
- phone: {
2814
- type: "string",
2815
- title: "Phone",
2816
- examples: ["555-123-4567"],
2817
- },
2818
- firstName: {
2819
- type: "string",
2820
- title: "First Name",
2821
- examples: ["John"],
2822
- },
2823
- lastName: {
2824
- type: "string",
2825
- title: "Last Name",
2826
- examples: ["Doe"],
2827
- },
2828
- fullName: {
2829
- type: "string",
2830
- title: "Full Name (calculated)",
2831
- },
2832
- deliveryType: {
2833
- type: "string",
2834
- title: "Delivery Type",
2835
- oneOf: [
2836
- { const: "standard", title: "Standard (5-7 days)" },
2837
- { const: "express", title: "Express (1-2 days)" },
2838
- { const: "pickup", title: "Pickup" },
2839
- ],
2840
- default: "standard",
2841
- },
2842
- quantity: {
2843
- type: "integer",
2844
- title: "Quantity",
2845
- minimum: 1,
2846
- default: 1,
2847
- },
2848
- unitPrice: {
2849
- type: "number",
2850
- title: "Unit Price",
2851
- default: 29.99,
2852
- },
2853
- subtotal: {
2854
- type: "number",
2855
- title: "Subtotal",
3069
+ properties: {
3070
+ type: {
3071
+ type: "string",
3072
+ title: "Select Account Type",
3073
+ oneOf: [
3074
+ { const: "personal", title: "Personal" },
3075
+ { const: "business", title: "Business" },
3076
+ ],
3077
+ default: "personal",
3078
+ },
3079
+ companyName: {
3080
+ type: "string",
3081
+ title: "Company Name",
3082
+ examples: ["Acme Inc."],
3083
+ },
3084
+ },
2856
3085
  },
2857
- shippingCost: {
2858
- type: "number",
2859
- title: "Shipping Cost",
3086
+ contactPreferences: {
3087
+ type: "object",
3088
+ title: "Contact Information",
3089
+ properties: {
3090
+ preferPhone: {
3091
+ type: "boolean",
3092
+ title: "I prefer to be contacted by phone",
3093
+ default: false,
3094
+ },
3095
+ email: {
3096
+ type: "string",
3097
+ format: "email",
3098
+ title: "Email",
3099
+ examples: ["you@example.com"],
3100
+ },
3101
+ phone: {
3102
+ type: "string",
3103
+ title: "Phone",
3104
+ examples: ["555-123-4567"],
3105
+ },
3106
+ },
2860
3107
  },
2861
- total: {
2862
- type: "number",
2863
- title: "Total",
3108
+ customerDetails: {
3109
+ type: "object",
3110
+ title: "Customer Details",
3111
+ properties: {
3112
+ firstName: {
3113
+ type: "string",
3114
+ title: "First Name",
3115
+ examples: ["John"],
3116
+ },
3117
+ lastName: {
3118
+ type: "string",
3119
+ title: "Last Name",
3120
+ examples: ["Doe"],
3121
+ },
3122
+ fullName: {
3123
+ type: "string",
3124
+ title: "Full Name (calculated)",
3125
+ },
3126
+ },
2864
3127
  },
2865
- shippingAddress: {
3128
+ deliveryOptions: {
2866
3129
  type: "object",
2867
- title: "Shipping Address",
3130
+ title: "Delivery & Shipping",
2868
3131
  properties: {
2869
- street: {
3132
+ deliveryType: {
2870
3133
  type: "string",
2871
- title: "Street",
2872
- examples: ["123 Main St"],
3134
+ title: "Delivery Type",
3135
+ oneOf: [
3136
+ { const: "standard", title: "Standard (5-7 days)" },
3137
+ { const: "express", title: "Express (1-2 days)" },
3138
+ { const: "pickup", title: "Pickup" },
3139
+ ],
3140
+ default: "standard",
3141
+ },
3142
+ shippingAddress: {
3143
+ type: "object",
3144
+ title: "Shipping Address",
3145
+ properties: {
3146
+ street: {
3147
+ type: "string",
3148
+ title: "Street",
3149
+ examples: ["123 Main St"],
3150
+ },
3151
+ city: { type: "string", title: "City", examples: ["New York"] },
3152
+ zip: { type: "string", title: "ZIP Code", examples: ["10001"] },
3153
+ },
3154
+ },
3155
+ },
3156
+ },
3157
+ orderCalculations: {
3158
+ type: "object",
3159
+ title: "Order Summary",
3160
+ properties: {
3161
+ quantity: {
3162
+ type: "integer",
3163
+ title: "Quantity",
3164
+ minimum: 1,
3165
+ default: 1,
3166
+ },
3167
+ unitPrice: {
3168
+ type: "number",
3169
+ title: "Unit Price",
3170
+ default: 29.99,
3171
+ },
3172
+ subtotal: {
3173
+ type: "number",
3174
+ title: "Subtotal",
3175
+ },
3176
+ shippingCost: {
3177
+ type: "number",
3178
+ title: "Shipping Cost",
3179
+ },
3180
+ total: {
3181
+ type: "number",
3182
+ title: "Total",
2873
3183
  },
2874
- city: { type: "string", title: "City", examples: ["New York"] },
2875
- zip: { type: "string", title: "ZIP Code", examples: ["10001"] },
2876
3184
  },
2877
3185
  },
2878
3186
  },
2879
- required: ["email", "firstName", "lastName", "deliveryType"],
3187
+ required: ["contactPreferences", "customerDetails", "deliveryOptions"],
2880
3188
  };
2881
3189
 
2882
3190
  const uiSchema = {
2883
- "ui:layout": "grid",
2884
- "ui:layoutOptions": { columns: 2, gap: "md" },
3191
+ "ui:layout": "accordion",
3192
+ "ui:layoutOptions": { openFirst: true },
2885
3193
 
2886
- "/companyName": {
2887
- "ui:visibleWhen": { "/accountType": "business" },
2888
- "ui:requiredWhen": { "/accountType": "business" },
2889
- },
2890
- "/email": {
2891
- "ui:disabledWhen": { "/preferPhone": true },
2892
- "ui:icon": "envelope",
2893
- },
2894
- "/phone": {
2895
- "ui:requiredWhen": { "/preferPhone": true },
2896
- "ui:icon": "phone",
2897
- },
2898
- "/fullName": {
2899
- "ui:calculate": { $concat: ["/firstName", " ", "/lastName"] },
3194
+ "/accountType": {
3195
+ "ui:layout": "stack",
3196
+ "companyName": {
3197
+ "ui:visibleWhen": { "/accountType/type": "business" },
3198
+ "ui:requiredWhen": { "/accountType/type": "business" },
3199
+ },
2900
3200
  },
2901
- "/subtotal": {
2902
- "ui:calculate": { $multiply: ["/quantity", "/unitPrice"] },
3201
+ "/contactPreferences": {
3202
+ "ui:layout": "stack",
3203
+ "email": {
3204
+ "ui:disabledWhen": { "/contactPreferences/preferPhone": true },
3205
+ "ui:icon": "envelope",
3206
+ },
3207
+ "phone": {
3208
+ "ui:requiredWhen": { "/contactPreferences/preferPhone": true },
3209
+ "ui:icon": "phone",
3210
+ },
2903
3211
  },
2904
- "/shippingCost": {
2905
- "ui:calculate": {
2906
- $if: {
2907
- cond: { "/deliveryType": "express" },
2908
- then: 25,
2909
- else: {
2910
- $if: {
2911
- cond: { "/deliveryType": "pickup" },
2912
- then: 0,
2913
- else: 10,
3212
+ "/customerDetails": {
3213
+ "ui:layout": "flex",
3214
+ "ui:layoutOptions": { gap: "md", wrap: true },
3215
+ "firstName": {},
3216
+ "lastName": {},
3217
+ "fullName": {
3218
+ "ui:calculate": { $concat: ["/customerDetails/firstName", " ", "/customerDetails/lastName"] },
3219
+ },
3220
+ },
3221
+ "/deliveryOptions": {
3222
+ "ui:layout": "stack",
3223
+ "shippingAddress": {
3224
+ "ui:visibleWhen": { "/deliveryOptions/deliveryType": { $ne: "pickup" } },
3225
+ "ui:layout": "flex",
3226
+ "ui:layoutOptions": { gap: "sm", wrap: true },
3227
+ },
3228
+ },
3229
+ "/orderCalculations": {
3230
+ "ui:layout": "stack",
3231
+ "subtotal": {
3232
+ "ui:calculate": { $multiply: ["/orderCalculations/quantity", "/orderCalculations/unitPrice"] },
3233
+ },
3234
+ "shippingCost": {
3235
+ "ui:calculate": {
3236
+ $if: {
3237
+ cond: { "/deliveryOptions/deliveryType": "express" },
3238
+ then: 25,
3239
+ else: {
3240
+ $if: {
3241
+ cond: { "/deliveryOptions/deliveryType": "pickup" },
3242
+ then: 0,
3243
+ else: 10,
3244
+ },
2914
3245
  },
2915
3246
  },
2916
3247
  },
2917
3248
  },
2918
- },
2919
- "/total": {
2920
- "ui:calculate": { $sum: ["/subtotal", "/shippingCost"] },
2921
- },
2922
- "/shippingAddress": {
2923
- "ui:visibleWhen": { "/deliveryType": { $ne: "pickup" } },
2924
- "ui:layout": "flex",
2925
- "ui:layoutOptions": { gap: "sm", wrap: true },
3249
+ "total": {
3250
+ "ui:calculate": { $sum: ["/orderCalculations/subtotal", "/orderCalculations/shippingCost"] },
3251
+ },
2926
3252
  },
2927
3253
  };
2928
3254
 
@@ -2931,28 +3257,19 @@ export const ConditionalComplex = {
2931
3257
  <p><strong>Try these interactions:</strong></p>
2932
3258
  <ul>
2933
3259
  <li>
2934
- Change <strong>Account Type</strong> to "Business" → Company Name
2935
- field appears and becomes required
2936
- </li>
2937
- <li>
2938
- Toggle <strong>"Prefer phone contact"</strong> → Email is disabled,
2939
- Phone becomes required
3260
+ Open <strong>Account Type</strong>, change to "Business" → Company Name field appears and becomes required
2940
3261
  </li>
2941
3262
  <li>
2942
- Type in <strong>First/Last Name</strong> → Full Name is calculated
2943
- automatically
3263
+ Open <strong>Contact Information</strong>, toggle <strong>"Prefer phone contact"</strong> → Email is disabled, Phone becomes required
2944
3264
  </li>
2945
3265
  <li>
2946
- Change <strong>Quantity</strong> or <strong>Unit Price</strong> →
2947
- Subtotal and Total update
3266
+ Open <strong>Customer Details</strong>, type in <strong>First/Last Name</strong> → Full Name is calculated automatically
2948
3267
  </li>
2949
3268
  <li>
2950
- Select <strong>Delivery Type</strong> → Shipping cost changes
2951
- (Express: $25, Standard: $10, Pickup: $0)
3269
+ Open <strong>Delivery & Shipping</strong>, select <strong>"Pickup"</strong> → Shipping Address section is hidden
2952
3270
  </li>
2953
3271
  <li>
2954
- Select <strong>"Pickup"</strong> → Shipping Address section is
2955
- hidden
3272
+ Open <strong>Order Summary</strong>, change <strong>Quantity</strong> or <strong>Delivery Type</strong> Costs update automatically
2956
3273
  </li>
2957
3274
  </ul>
2958
3275
  </div>
@@ -2960,7 +3277,10 @@ export const ConditionalComplex = {
2960
3277
  data-required
2961
3278
  .jsonSchema=${schema}
2962
3279
  .uiSchema=${uiSchema}
2963
- @pw:submit=${(e) => toastFormData(e.detail)}
3280
+ @pw:submit=${(e) => {
3281
+ console.log('✅ Form Data Structure:', e.detail);
3282
+ toastFormData(e.detail);
3283
+ }}
2964
3284
  ></pds-form>
2965
3285
  `;
2966
3286
  },