@dmitryvim/form-builder 0.1.38 → 0.1.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/demo.js CHANGED
@@ -5,6 +5,16 @@
5
5
  const EXAMPLE_SCHEMA = {
6
6
  version: "0.3",
7
7
  title: "Asset Uploader with Slides",
8
+ actions: [
9
+ {
10
+ key: "test-missing",
11
+ label: "Другая потеря"
12
+ },
13
+ {
14
+ key: "save-draft",
15
+ label: "Сохранить черновик"
16
+ }
17
+ ],
8
18
  elements: [
9
19
  {
10
20
  type: "file",
@@ -797,6 +807,13 @@ const EXAMPLE_ACTIONS = [
797
807
  value: "save_form_draft",
798
808
  label: "💾 Save Draft",
799
809
  },
810
+ // Action with missing related field (should appear at bottom of form)
811
+ {
812
+ related_field: "non_existent_field",
813
+ key: "test-missing",
814
+ value: "test_missing_field_action",
815
+ label: "🔍 Test Missing Field Action",
816
+ },
800
817
  {
801
818
  key: "preview",
802
819
  value: "preview_infographic",
@@ -998,14 +998,15 @@ function renderExternalActions() {
998
998
 
999
999
  // Group actions by related_field (null for form-level actions)
1000
1000
  const actionsByField = new Map();
1001
- const formLevelActions = [];
1001
+ const trueFormLevelActions = [];
1002
+ const movedFormLevelActions = [];
1002
1003
 
1003
1004
  state.externalActions.forEach((action) => {
1004
1005
  if (!action.key || !action.value) return;
1005
1006
 
1006
1007
  if (!action.related_field) {
1007
- // Form-level action
1008
- formLevelActions.push(action);
1008
+ // True form-level action
1009
+ trueFormLevelActions.push(action);
1009
1010
  } else {
1010
1011
  // Field-level action
1011
1012
  if (!actionsByField.has(action.related_field)) {
@@ -1021,8 +1022,10 @@ function renderExternalActions() {
1021
1022
  const fieldElement = findFormElementByFieldPath(fieldPath);
1022
1023
  if (!fieldElement) {
1023
1024
  console.warn(
1024
- `External action: Could not find form element for field "${fieldPath}"`,
1025
+ `External action: Could not find form element for field "${fieldPath}", treating as form-level actions`,
1025
1026
  );
1027
+ // If field is not found, treat these actions as moved form-level actions
1028
+ movedFormLevelActions.push(...actions);
1026
1029
  return;
1027
1030
  }
1028
1031
 
@@ -1087,12 +1090,13 @@ function renderExternalActions() {
1087
1090
  });
1088
1091
 
1089
1092
  // Render form-level actions at the bottom of the form
1090
- if (formLevelActions.length > 0) {
1091
- renderFormLevelActions(formLevelActions);
1093
+ const allFormLevelActions = [...trueFormLevelActions, ...movedFormLevelActions];
1094
+ if (allFormLevelActions.length > 0) {
1095
+ renderFormLevelActions(allFormLevelActions, trueFormLevelActions);
1092
1096
  }
1093
1097
  }
1094
1098
 
1095
- function renderFormLevelActions(actions) {
1099
+ function renderFormLevelActions(actions, trueFormLevelActions = []) {
1096
1100
  if (!state.formRoot) return;
1097
1101
 
1098
1102
  // Remove any existing form-level actions container
@@ -1113,11 +1117,14 @@ function renderFormLevelActions(actions) {
1113
1117
  actionBtn.className =
1114
1118
  "bg-white text-gray-700 border border-gray-200 px-4 py-2 text-sm font-medium rounded-lg hover:bg-gray-50 hover:border-gray-300 transition-all duration-200 shadow-sm";
1115
1119
 
1120
+ // Check if this is a true form-level action (no related_field originally)
1121
+ const isTrueFormLevelAction = trueFormLevelActions.includes(action);
1122
+
1116
1123
  // Resolve action label with priority:
1117
1124
  // 1. Use explicit label from action if provided
1118
- // 2. Try to find label from schema element labels using key
1125
+ // 2. Try to find label from schema element labels using key (only for true form-level actions)
1119
1126
  // 3. Fall back to using key as label
1120
- const resolvedLabel = resolveActionLabel(action.key, action.label, null);
1127
+ const resolvedLabel = resolveActionLabel(action.key, action.label, null, isTrueFormLevelAction);
1121
1128
  actionBtn.textContent = resolvedLabel;
1122
1129
 
1123
1130
  actionBtn.addEventListener("click", (e) => {
@@ -1141,8 +1148,8 @@ function renderFormLevelActions(actions) {
1141
1148
  }
1142
1149
 
1143
1150
  // Helper function to resolve action label
1144
- function resolveActionLabel(actionKey, externalLabel, schemaElement) {
1145
- // 1. Try to find label from predefined actions in schema using key (highest priority)
1151
+ function resolveActionLabel(actionKey, externalLabel, schemaElement, isTrueFormLevelAction = false) {
1152
+ // 1. Try to find label from predefined actions in schema element using key (highest priority)
1146
1153
  if (schemaElement && schemaElement.actions && Array.isArray(schemaElement.actions)) {
1147
1154
  const predefinedAction = schemaElement.actions.find(a => a.key === actionKey);
1148
1155
  if (predefinedAction && predefinedAction.label) {
@@ -1150,12 +1157,20 @@ function resolveActionLabel(actionKey, externalLabel, schemaElement) {
1150
1157
  }
1151
1158
  }
1152
1159
 
1153
- // 2. Use explicit label from external action if provided
1160
+ // 2. Try to find label from root-level schema actions (only for true form-level actions)
1161
+ if (isTrueFormLevelAction && state.schema && state.schema.actions && Array.isArray(state.schema.actions)) {
1162
+ const rootAction = state.schema.actions.find(a => a.key === actionKey);
1163
+ if (rootAction && rootAction.label) {
1164
+ return rootAction.label;
1165
+ }
1166
+ }
1167
+
1168
+ // 3. Use explicit label from external action if provided
1154
1169
  if (externalLabel) {
1155
1170
  return externalLabel;
1156
1171
  }
1157
1172
 
1158
- // 3. Fall back to using key as label
1173
+ // 4. Fall back to using key as label
1159
1174
  return actionKey;
1160
1175
  }
1161
1176
 
@@ -2643,7 +2658,7 @@ function addPrefillFilesToIndex(initialFiles) {
2643
2658
  if (!state.resourceIndex.has(resourceId)) {
2644
2659
  // Extract filename from URL/path
2645
2660
  const filename = resourceId.split("/").pop() || "file";
2646
- // Determine file type from extension
2661
+ // Determine file type from extension (excluding PSD from image types)
2647
2662
  const extension = filename.split(".").pop()?.toLowerCase();
2648
2663
  const fileType =
2649
2664
  extension && ["jpg", "jpeg", "png", "gif", "webp"].includes(extension)
@@ -3064,19 +3079,31 @@ function renderFilePreviewReadonly(resourceId, fileName) {
3064
3079
  const actualFileName =
3065
3080
  fileName || meta?.name || resourceId.split("/").pop() || "file";
3066
3081
 
3082
+ // Determine if this looks like a PSD file (should be treated as download, not preview)
3083
+ const isPSD = actualFileName.toLowerCase().match(/\.psd$/);
3084
+
3067
3085
  // Individual file result container
3068
3086
  const fileResult = document.createElement("div");
3069
- fileResult.className = "space-y-3";
3087
+ fileResult.className = isPSD ? "space-y-2" : "space-y-3";
3070
3088
 
3071
- // Large preview container
3089
+ // Preview container - compact for PSD files, large for others
3072
3090
  const previewContainer = document.createElement("div");
3073
- previewContainer.className =
3074
- "bg-gray-100 rounded-lg overflow-hidden cursor-pointer hover:opacity-90 transition-opacity";
3091
+ if (isPSD) {
3092
+ // Compact container for PSD files
3093
+ previewContainer.className =
3094
+ "bg-gray-100 rounded-lg overflow-hidden cursor-pointer hover:opacity-90 transition-opacity flex items-center p-3 max-w-sm";
3095
+ } else {
3096
+ // Large container for images/videos
3097
+ previewContainer.className =
3098
+ "bg-gray-100 rounded-lg overflow-hidden cursor-pointer hover:opacity-90 transition-opacity";
3099
+ }
3075
3100
 
3076
- // Determine if this looks like an image file
3101
+ // Determine if this looks like an image file (excluding PSD files)
3077
3102
  const isImage =
3078
- meta?.type?.startsWith("image/") ||
3079
- actualFileName.toLowerCase().match(/\.(jpg|jpeg|png|gif|webp)$/);
3103
+ !isPSD && (
3104
+ meta?.type?.startsWith("image/") ||
3105
+ actualFileName.toLowerCase().match(/\.(jpg|jpeg|png|gif|webp)$/)
3106
+ );
3080
3107
 
3081
3108
  // Determine if this looks like a video file
3082
3109
  const isVideo =
@@ -3133,13 +3160,30 @@ function renderFilePreviewReadonly(resourceId, fileName) {
3133
3160
  previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">🎥</div><div class="text-sm">${actualFileName}</div></div></div>`;
3134
3161
  }
3135
3162
  } else {
3136
- // Other file types
3137
- previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">📁</div><div class="text-sm">${actualFileName}</div></div></div>`;
3163
+ // Other file types - special handling for PSD files
3164
+ const fileIcon = isPSD ? "🎨" : "📁";
3165
+ const fileDescription = isPSD ? "PSD File" : "Document";
3166
+
3167
+ if (isPSD) {
3168
+ // Compact horizontal layout for PSD files
3169
+ previewContainer.innerHTML = `
3170
+ <div class="flex items-center space-x-3">
3171
+ <div class="text-3xl text-gray-400">${fileIcon}</div>
3172
+ <div class="flex-1 min-w-0">
3173
+ <div class="text-sm font-medium text-gray-900 truncate">${actualFileName}</div>
3174
+ <div class="text-xs text-gray-500">${fileDescription}</div>
3175
+ </div>
3176
+ </div>
3177
+ `;
3178
+ } else {
3179
+ // Large centered layout for other documents
3180
+ previewContainer.innerHTML = `<div class="aspect-video flex items-center justify-center text-gray-400"><div class="text-center"><div class="text-4xl mb-2">${fileIcon}</div><div class="text-sm">${actualFileName}</div><div class="text-xs text-gray-500 mt-1">${fileDescription}</div></div></div>`;
3181
+ }
3138
3182
  }
3139
3183
 
3140
- // File name
3184
+ // File name (only show for non-PSD files since PSD files show name inline)
3141
3185
  const fileNameElement = document.createElement("p");
3142
- fileNameElement.className = "text-sm font-medium text-gray-900 text-center";
3186
+ fileNameElement.className = isPSD ? "hidden" : "text-sm font-medium text-gray-900 text-center";
3143
3187
  fileNameElement.textContent = actualFileName;
3144
3188
 
3145
3189
  // Download button
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.38",
6
+ "version": "0.1.40",
7
7
  "description": "A reusable JSON schema form builder library",
8
8
  "main": "dist/form-builder.js",
9
9
  "module": "dist/form-builder.js",