@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 +17 -0
- package/dist/form-builder.js +69 -25
- package/package.json +1 -1
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",
|
package/dist/form-builder.js
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
1008
|
-
|
|
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
|
-
|
|
1091
|
-
|
|
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.
|
|
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
|
-
//
|
|
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
|
-
//
|
|
3089
|
+
// Preview container - compact for PSD files, large for others
|
|
3072
3090
|
const previewContainer = document.createElement("div");
|
|
3073
|
-
|
|
3074
|
-
|
|
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
|
-
|
|
3079
|
-
|
|
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
|
-
|
|
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
|