├── .eslintignore
├── .eslintrc.cjs
├── .github
├── FUNDING.yml
└── workflows
│ ├── community-publish.yml
│ └── community-push-event.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .vscode
└── settings.json
├── LICENSE
├── LICENSE_COMMERCIAL
├── NOTICES
├── README.md
├── package-lock.json
├── package.json
├── src
├── app.d.ts
├── app.html
├── lib
│ ├── ComponentSelection
│ │ ├── ComponentSelection.svelte
│ │ ├── DragNDropComponentSelection.ts
│ │ └── PinnedComponentSelection.svelte
│ ├── Components
│ │ ├── Audio.svelte
│ │ ├── Button.svelte
│ │ ├── Canvas.svelte
│ │ ├── Checkbox Group.svelte
│ │ ├── Color.svelte
│ │ ├── Date.svelte
│ │ ├── DateTime.svelte
│ │ ├── Divider.svelte
│ │ ├── File Upload.svelte
│ │ ├── Header.svelte
│ │ ├── Hidden.svelte
│ │ ├── Link.svelte
│ │ ├── Meter.svelte
│ │ ├── Month.svelte
│ │ ├── Number.svelte
│ │ ├── Paragraph.svelte
│ │ ├── Password.svelte
│ │ ├── Picture.svelte
│ │ ├── Progress.svelte
│ │ ├── Radio Group.svelte
│ │ ├── Range.svelte
│ │ ├── Select.svelte
│ │ ├── Stars.svelte
│ │ ├── Text Area.svelte
│ │ ├── Text.svelte
│ │ ├── Time.svelte
│ │ ├── Video.svelte
│ │ └── Week.svelte
│ ├── Form
│ │ ├── DragNDrop
│ │ │ ├── DragNDrop.ts
│ │ │ ├── LeftRight.svelte
│ │ │ └── TopBottom.svelte
│ │ ├── PropertyPanel
│ │ │ ├── PropertyPanel.svelte
│ │ │ ├── PropertyPanelChoices.svelte
│ │ │ ├── PropertyPanelComponentSpecific
│ │ │ │ ├── ButtonSpecific.svelte
│ │ │ │ ├── PropertyPanelChoiceCheckboxRadioSpecific.svelte
│ │ │ │ ├── PropertyPanelMatrixSpecific.svelte
│ │ │ │ └── Table
│ │ │ │ │ ├── Editors
│ │ │ │ │ ├── InputEditor.svelte
│ │ │ │ │ └── ListEditor.svelte
│ │ │ │ │ ├── Formatters
│ │ │ │ │ ├── ImageFormatter.svelte
│ │ │ │ │ ├── MoneyFormatter.svelte
│ │ │ │ │ └── StarFormatter.svelte
│ │ │ │ │ ├── TableColumnSpecific.svelte
│ │ │ │ │ └── TableSpecific.svelte
│ │ │ ├── PropertyPanelDataAttributes.svelte
│ │ │ ├── PropertyPanelHtmlAttributes.svelte
│ │ │ ├── PropertyPanelLabel.svelte
│ │ │ ├── PropertyPanelTooltip.svelte
│ │ │ └── PropertyPanelUtilities
│ │ │ │ ├── Checkbox.svelte
│ │ │ │ ├── Label.svelte
│ │ │ │ ├── SelectSlot.svelte
│ │ │ │ └── TextSlot.svelte
│ │ └── QuickMenu
│ │ │ ├── QuickMenu.svelte
│ │ │ └── QuickMenuUtils.ts
│ ├── FormBuilder.svelte
│ ├── Tabs
│ │ ├── DragNDrop.ts
│ │ ├── QuickMenu.svelte
│ │ ├── TabHeader.svelte
│ │ ├── TabManager.ts
│ │ └── TabPropertyPanel.svelte
│ ├── Tools
│ │ ├── BuildTools.svelte
│ │ ├── RenderTools.svelte
│ │ └── Tools.svelte
│ ├── Utils
│ │ ├── ComponentUtilities
│ │ │ ├── CheckboxRadioCommon.svelte
│ │ │ ├── ComponentLabel.svelte
│ │ │ ├── GroupSlot.svelte
│ │ │ └── SelectOptions.svelte
│ │ ├── Misc
│ │ │ ├── Theme.ts
│ │ │ └── flavor.ts
│ │ ├── MiscComponents
│ │ │ ├── DisplayContentsWrapper.svelte
│ │ │ ├── DragImage.svelte
│ │ │ ├── DropdownMenu.svelte
│ │ │ ├── Icon.svelte
│ │ │ ├── Loader.svelte
│ │ │ ├── ScrollToElementFork
│ │ │ │ ├── helper.ts
│ │ │ │ └── service.ts
│ │ │ ├── StarFork
│ │ │ │ ├── Star.svelte
│ │ │ │ └── StarRating2.svelte
│ │ │ └── StyledSidePanel.svelte
│ │ ├── Utils.ts
│ │ ├── other-types
│ │ │ ├── svelte-types.d.ts
│ │ │ └── vite-env.d.ts
│ │ ├── store.ts
│ │ └── types.ts
│ ├── Views
│ │ ├── Conditions.svelte
│ │ ├── Form.svelte
│ │ ├── Header.svelte
│ │ └── Settings.svelte
│ ├── assets
│ │ └── svelte.png
│ ├── index.ts
│ └── lib
│ │ ├── API
│ │ ├── BuilderAPI.ts
│ │ └── RenderAPI.ts
│ │ ├── ConditionManager.ts
│ │ ├── DefaultAttributeMap.ts
│ │ ├── DefinitionManager.ts
│ │ ├── OptionsProcessor.ts
│ │ ├── RenderManager.ts
│ │ └── Validation.ts
└── routes
│ └── +page.svelte
├── static
└── favicon.png
├── svelte.config.js
├── tsconfig.json
└── vite.config.ts
/.eslintignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
10 | # Ignore files for PNPM, NPM and YARN
11 | pnpm-lock.yaml
12 | package-lock.json
13 | yarn.lock
14 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@typescript-eslint/parser',
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'prettier',
8 | 'plugin:svelte/recommended'
9 | ],
10 | plugins: ['@typescript-eslint'],
11 | ignorePatterns: ['*.cjs'],
12 | overrides: [
13 | {
14 | files: ['*.svelte'],
15 | parser: 'svelte-eslint-parser',
16 | // Parse the `
5 |
6 | {#if !$componentSelectionPoppedOut}
7 |
dragLeave(e)}
48 | on:dragenter={(e) => dragEnter(e)}
49 | on:dragover={(e) => dragOver(e)}
50 | on:drop
51 | />
52 |
53 |
61 |
--------------------------------------------------------------------------------
/src/lib/Form/DragNDrop/TopBottom.svelte:
--------------------------------------------------------------------------------
1 |
49 |
50 |
dragLeave(e)}
55 | on:dragenter={(e) => dragEnter(e)}
56 | on:dragover={(e) => dragOver(e)}
57 | on:drop
58 | >
59 | {text}
63 |
64 |
65 |
73 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanel.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
108 |
109 | {#if $showPropertyPanel}
110 |
111 |
112 |
124 |
125 |
126 |
127 |
128 |
129 |
132 |
133 | {#if field.componentName == 'Button'}
134 |
135 | {/if}
136 |
137 |
138 |
139 | {#if field.htmlAttributes}
140 | {#each Object.entries(field.htmlAttributes) as [name, value]}
141 | {#if selectedLink == 'General' && !$opts.disabledHtmlAttributes?.includes(name) && !fieldComponent.componentOptions.disabledHtmlAttributes?.includes(name)}
142 |
143 | {/if}
144 | {/each}
145 | {/if}
146 |
147 |
148 | {#if hasChoices}
149 |
150 | {/if}
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 | {#if field.componentName == 'Checkbox Group' || field.componentName == 'Radio Group' || field.componentName == 'Matrix'}
159 |
160 | {/if}
161 |
162 |
163 | {#if field.componentName == 'Table'}
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 | {/if}
172 |
173 | {#if field.componentName == 'Matrix'}
174 |
175 |
176 |
177 | {/if}
178 |
179 |
180 |
181 |
182 | {#if field.dataAttributes}
183 | {#each field.dataAttributes as item}
184 | {#if !item.tab && selectedLink == 'General'}
185 |
186 | {:else if item.tab == selectedLink}
187 |
188 | {/if}
189 | {/each}
190 | {/if}
191 |
192 |
193 |
194 | {/if}
195 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelChoices.svelte:
--------------------------------------------------------------------------------
1 |
118 |
119 | {#if choiceConfiguration && field.componentName == 'Select'}
120 |
125 | {/if}
126 |
127 | {#if choiceConfiguration && (field.componentName == 'Checkbox Group' || field.componentName == 'Radio Group')}
128 |
133 | {/if}
134 |
135 | {#if choiceConfiguration && (field.componentName == 'AutoComplete' || field.componentName == 'Table')}
136 |
141 | {/if}
142 |
143 |
144 |
145 |
Label
146 |
Value
147 |
148 | {#if choiceConfiguration && choiceConfiguration.choices}
149 | {#each choiceConfiguration.choices as choice, i}
150 |
deleteChoice(choice)}
151 | >
153 |
onKeyDownLabel(e)}
158 | />
159 |
onKeyDownValue(e)}
164 | />
165 | {/each}
166 | {/if}
167 |
168 |
169 |
170 | addChoice()}>
171 |
172 | Add
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 | (showBulkAdd = true)}>Bulk Add
184 | {
187 | if (choiceConfiguration) {
188 | choiceConfiguration.choices = undefined;
189 | }
190 | // https://github.com/sveltejs/svelte/issues/7850
191 | // setTimeout(() => {
192 | // valueElements = [];
193 | // labelElements = [];
194 | // }, 0);
195 | }}>Clear All
197 |
198 |
199 | {#if showBulkAdd}
200 |
201 | Paste a list of items
202 | Optionally use | to split Label|Value
203 |
204 |
205 |
206 |
207 | Save
208 |
209 | {/if}
210 |
211 |
243 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelComponentSpecific/ButtonSpecific.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 | button
16 | submit
17 | reset
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelComponentSpecific/PropertyPanelChoiceCheckboxRadioSpecific.svelte:
--------------------------------------------------------------------------------
1 |
41 |
42 | {#if field.choiceConfiguration}
43 |
44 | {#key field.choiceConfiguration}
45 |
46 |
47 |
48 | {/key}
49 |
50 |
51 | {#if field.componentName == 'Checkbox Group' && !field.choiceConfiguration?.isSwitch}
52 |
53 |
58 |
59 | {/if}
60 |
61 | {#if !field.choiceConfiguration?.checkIcon}
62 |
63 |
68 |
69 | {/if}
70 |
71 | {#if field.componentName != 'Matrix'}
72 |
73 |
78 |
79 | {/if}
80 |
81 | {#if field.choiceConfiguration.isSwitch && !field.choiceConfiguration?.checkIcon}
82 |
83 |
84 |
85 |
86 |
87 | {#each ChoiceSwitchStyle as x}
88 | {x}
89 | {/each}
90 |
91 |
92 | {/if}
93 |
94 |
95 |
96 |
97 | default
98 |
99 | {#each ChoiceColorStyle as x}
100 | {x}
101 | {/each}
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | fill
112 |
113 |
114 | {#if !field.choiceConfiguration.checkIcon}
115 | thick
116 | {/if}
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | smooth
126 | pulse
127 |
128 | {#if field.choiceConfiguration.svg || field.choiceConfiguration.isSwitch}
129 | tada
130 | jelly
131 | rotate
132 | {/if}
133 |
134 |
135 | {/if}
136 |
137 |
148 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelComponentSpecific/PropertyPanelMatrixSpecific.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 | Rows
11 |
16 |
17 |
18 |
19 | Columns
20 |
25 |
26 |
27 |
34 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelComponentSpecific/Table/Editors/InputEditor.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
16 |
17 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelComponentSpecific/Table/Editors/ListEditor.svelte:
--------------------------------------------------------------------------------
1 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelComponentSpecific/Table/Formatters/ImageFormatter.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
16 |
17 |
18 |
19 |
20 |
25 |
26 |
27 |
28 |
29 |
34 |
35 |
36 |
37 |
38 |
43 |
44 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelComponentSpecific/Table/Formatters/MoneyFormatter.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
17 |
18 |
19 |
20 |
21 |
27 |
28 |
29 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelComponentSpecific/Table/Formatters/StarFormatter.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
16 |
17 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelComponentSpecific/Table/TableColumnSpecific.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
173 |
174 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 | Input
193 |
194 | Number
195 | Checkbox
196 | Star Rating
197 | Date
198 | List
199 |
200 |
201 |
202 |
203 | {#if editor == 'input' && formatter == 'plaintext'}
204 |
205 | {/if}
206 |
207 |
208 |
209 |
210 |
211 |
212 | Plain Text
213 | HTML
214 | Money
215 | Link
216 | Image
217 | Color
218 | Row Number
219 |
220 |
221 |
222 |
223 |
224 | {#if formatter == 'money'}
225 |
226 | {:else if formatter == 'image'}
227 |
228 | {:else if formatter == 'star'}
229 |
230 | {/if}
231 |
232 | {#if editor == 'list'}
233 |
234 | {/if}
235 |
236 |
242 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelComponentSpecific/Table/TableSpecific.svelte:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelDataAttributes.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 | {#if dataAttribute && dataAttribute.label}
14 | {#if dataAttribute.isButton}
15 |
dataAttribute.buttonHandler && dataAttribute.buttonHandler(e, dataAttribute)}
17 | >{dataAttribute.label}
19 | {/if}
20 |
21 | {#if dataAttribute.options}
22 |
23 |
24 |
25 | {#if !dataAttribute.value && !Array.isArray(dataAttribute.value)}
26 | {(dataAttribute.value = [])}
27 | {/if}
28 |
29 | {#if dataAttribute.multiple}
30 |
31 | {#each dataAttribute.options as option}
32 | {option.label}
33 | {/each}
34 |
35 | {:else}
36 |
37 | {#each dataAttribute.options as option}
38 | {option.label}
39 | {/each}
40 |
41 | {/if}
42 |
43 | {:else if dataAttribute.valueType == 'boolean' || typeof dataAttribute.value === 'boolean'}
44 |
45 | {:else if dataAttribute.valueType == 'string' || typeof dataAttribute.value === 'string'}
46 |
47 |
48 |
49 |
50 | {:else if dataAttribute.valueType == 'number' || typeof dataAttribute.value === 'number'}
51 |
52 |
53 |
54 |
55 | {/if}
56 | {/if}
57 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelHtmlAttributes.svelte:
--------------------------------------------------------------------------------
1 |
15 |
16 | {#if typeof value === 'boolean'}
17 |
18 | {/if}
19 |
20 | {#if typeof value === 'string'}
21 |
22 |
23 |
24 |
25 | {/if}
26 |
27 |
28 | {#if typeof value === 'number' || value == null}
29 |
30 |
31 |
32 |
33 | {/if}
34 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelLabel.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 | {#if field.labelAttributes}
12 |
13 |
14 |
20 |
21 |
22 | {#if $opts.showLabelMaxWidth}
23 |
24 |
25 |
31 |
32 | {/if}
33 |
34 | {#if $opts.showLabelStyle}
35 |
36 |
37 |
43 |
44 | {/if}
45 | {/if}
46 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelTooltip.svelte:
--------------------------------------------------------------------------------
1 |
28 |
29 | {#if field.tooltipAttributes}
30 |
31 |
32 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | {#each TooltipPosition as position}
45 | {position}
46 | {/each}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | {#each TooltipSize as size}
55 | {size}
56 | {/each}
57 |
58 |
59 | {/if}
60 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelUtilities/Checkbox.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
23 |
24 |
25 |
26 |
{label}
27 |
28 |
29 |
30 |
47 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelUtilities/Label.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
{label}
9 |
10 |
11 |
12 |
17 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelUtilities/SelectSlot.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
--------------------------------------------------------------------------------
/src/lib/Form/PropertyPanel/PropertyPanelUtilities/TextSlot.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
--------------------------------------------------------------------------------
/src/lib/Form/QuickMenu/QuickMenu.svelte:
--------------------------------------------------------------------------------
1 |
18 |
19 |
69 |
70 |
83 |
--------------------------------------------------------------------------------
/src/lib/Form/QuickMenu/QuickMenuUtils.ts:
--------------------------------------------------------------------------------
1 | import type { Field, FormDefinition } from '$lib/Utils/types';
2 | import {
3 | isPointerOverFieldStore,
4 | focusedField,
5 | propertyField,
6 | showPropertyPanel,
7 | opts
8 | } from '$lib/Utils/store';
9 | import { get } from 'svelte/store';
10 | import { CScope } from '$lib/Utils/Utils';
11 |
12 | export class QuickMenuUtils {
13 | constructor(public definition: FormDefinition) {}
14 |
15 | static editField(field: Field) {
16 | const isShown = get(showPropertyPanel);
17 |
18 | const existingField = get(propertyField);
19 | if (existingField != field) {
20 | propertyField.set(field);
21 | showPropertyPanel.set(true);
22 | } else {
23 | showPropertyPanel.set(!isShown);
24 | }
25 | }
26 | static pointerOver(e: PointerEvent, field: Field) {
27 | isPointerOverFieldStore.set(true);
28 |
29 | const paths = e.composedPath();
30 | paths.forEach((path) => {
31 | if ((path as HTMLDivElement).classList) {
32 | if ((path as HTMLDivElement).classList.contains(CScope('control'))) {
33 | // (path as HTMLDivElement).classList.add('pointerOverField');
34 | (path as HTMLDivElement).style.border = get(opts).styling?.form
35 | ?.pointerOverComponentBorder as string;
36 | }
37 | }
38 | });
39 |
40 | focusedField.set(field);
41 | }
42 | static pointerLeave(e: PointerEvent, field: Field) {
43 | // (e.target as HTMLDivElement).classList.remove('pointerOverField');
44 | (e.target as HTMLDivElement).style.border = 'unset';
45 | isPointerOverFieldStore.set(false);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/lib/Tabs/DragNDrop.ts:
--------------------------------------------------------------------------------
1 | import { isTabDragging } from '$lib/Utils/store';
2 | import type { FormTab } from '$lib/Utils/types';
3 | import { CScope } from '$lib/Utils/Utils';
4 |
5 | export class DragNDropTabs {
6 | dragSource_Tab: FormTab | null = null;
7 | dragSource_Element: HTMLElement | null = null;
8 |
9 | constructor() {}
10 |
11 | tabDragStart(event: DragEvent, tab: FormTab | undefined) {
12 | this.dragSource_Tab = tab as FormTab;
13 | this.dragSource_Element = event.target as HTMLElement;
14 |
15 | this.dragSource_Element.classList.add('isDraggingTab');
16 | event.dataTransfer?.setDragImage(
17 | document.getElementById(CScope('tabdragimage')) as HTMLElement,
18 | 16,
19 | 16
20 | );
21 |
22 | isTabDragging.set(true);
23 | }
24 |
25 | tabDragOver(event: DragEvent, tab: FormTab | undefined) {
26 | //Ignore same tab
27 | if (this.dragSource_Element == (event.currentTarget as HTMLElement)) {
28 | return;
29 | }
30 |
31 | event.preventDefault();
32 |
33 | if (event.target) {
34 | const targetShape = (event.target as HTMLElement).getBoundingClientRect();
35 | const isOverHalfwayAcrossXAxis = event.clientX > targetShape.x + targetShape.width / 2;
36 |
37 | if (isOverHalfwayAcrossXAxis && this.dragSource_Element) {
38 | swap(this.dragSource_Element, event.currentTarget as HTMLElement);
39 | }
40 | }
41 | }
42 |
43 | tabDragEnd(event: DragEvent, tab: FormTab | undefined) {
44 | if (this.dragSource_Element) {
45 | this.dragSource_Element.classList.remove('isDraggingTab');
46 | }
47 |
48 | isTabDragging.set(false);
49 | }
50 | }
51 |
52 | const swap = function (nodeA: HTMLElement, nodeB: HTMLElement) {
53 | const parentA = nodeA.parentNode;
54 | const siblingA = nodeA.nextSibling === nodeB ? nodeA : nodeA.nextSibling;
55 |
56 | // Move `nodeA` to before the `nodeB`
57 | nodeB.parentNode && nodeB.parentNode.insertBefore(nodeA, nodeB);
58 |
59 | // Move `nodeB` to before the sibling of `nodeA`
60 | parentA && parentA.insertBefore(nodeB, siblingA);
61 | };
62 |
--------------------------------------------------------------------------------
/src/lib/Tabs/QuickMenu.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 | {
15 | $tabPropertiesOpen = !$tabPropertiesOpen;
16 | }}
17 | enableAlternate={true}
18 | alternateFill="blue"
19 | size="20px"
20 | />
21 | {
24 | TabManager.deleteTab(tab, $opts.confirmRemoveTab);
25 | }}
26 | enableAlternate={true}
27 | alternateFill="red"
28 | size="20px"
29 | marginLeft={'10px'}
30 | />
31 |
32 |
33 | {#if $tabPropertiesOpen}
34 |
35 | {/if}
36 |
--------------------------------------------------------------------------------
/src/lib/Tabs/TabHeader.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 | {#if $view != 'settings' && $view != 'conditions'}
18 |
19 | {#each $mainDefinition as formDefinition}
20 | {#if formDefinition.tab}
21 | $view == 'build' && DnD.tabDragStart(e, formDefinition.tab)}
25 | on:dragover={(e) => $view == 'build' && DnD.tabDragOver(e, formDefinition.tab)}
26 | on:dragend={(e) => $view == 'build' && DnD.tabDragEnd(e, formDefinition.tab)}
27 | draggable={$view == 'build' && !$opts.disableDragNDropTabs}
28 | class={$opts.activeTabOrderValue === formDefinition.tab.tabOrder ? 'active' : ''}
29 | >
30 |
32 | TabManager.activateTab(formDefinition.tab && formDefinition.tab.tabOrder)}
33 | on:pointerover={() => {
34 | isPointerOverTabHeader = true;
35 | pointerOverTab = formDefinition.tab;
36 | }}
37 | on:pointerleave={() => (isPointerOverTabHeader = false)}
38 | >
39 | {formDefinition.tab.label}
40 |
41 | {#if $view == 'build'}
42 | {#if isPointerOverTabHeader && formDefinition.tab == pointerOverTab && $opts.activeTabOrderValue === formDefinition.tab.tabOrder}
43 |
44 | {/if}
45 |
46 | {#if $tabPropertiesOpen && $opts.activeTabOrderValue === formDefinition.tab.tabOrder}
47 |
48 | {/if}
49 | {/if}
50 |
51 |
52 | {/if}
53 | {/each}
54 |
55 |
56 | {#if $view == 'build'}
57 |
58 | TabManager.addTab()}>
59 |
60 |
61 |
62 | {/if}
63 |
64 | {/if}
65 |
66 |
101 |
--------------------------------------------------------------------------------
/src/lib/Tabs/TabManager.ts:
--------------------------------------------------------------------------------
1 | import { get } from 'svelte/store';
2 | import { mainDefinition, opts } from '$lib/Utils/store';
3 | import type { FormDefinition, FormTab, BuilderOptions } from '$lib/Utils/types';
4 | import { LibraryPrefix } from '$lib/Utils/Utils';
5 |
6 | export class TabManager {
7 | constructor(public opts: BuilderOptions) {}
8 |
9 | static getActiveTabDefinition() {
10 | return get(mainDefinition).find(
11 | (x) => x.tab?.tabOrder == get(opts).activeTabOrderValue
12 | ) as FormDefinition;
13 | }
14 |
15 | static numTabs() {
16 | return get(mainDefinition).length;
17 | }
18 |
19 | static addTab() {
20 | const options = get(opts);
21 |
22 | const len = get(mainDefinition).length + 1;
23 |
24 | const tab: FormTab = { label: len.toString(), id: len.toString(), tabOrder: len };
25 | this.MergeTabAttributes(tab);
26 |
27 | const definitions = get(mainDefinition);
28 | definitions.push({
29 | tab: tab,
30 | rows: []
31 | });
32 |
33 | mainDefinition.set(definitions);
34 |
35 | options.activeTabOrderValue = len;
36 | opts.set(options);
37 |
38 | if (get(opts).builderAPIEvents?.onTabAdded) {
39 | get(opts).builderAPIEvents?.onTabAdded?.call(this, tab);
40 | }
41 | }
42 |
43 | //Merge any attributes defined in tabDataAttributes into the tab
44 | static MergeTabAttributes(tab: FormTab | undefined) {
45 | const options = get(opts);
46 |
47 | if (options.tabDataAttributes && tab) {
48 | if (!Array.isArray(tab.dataAttributes)) {
49 | tab.dataAttributes = [];
50 | }
51 |
52 | options.tabDataAttributes.forEach((attribute) => {
53 | //Overriding attributes that come from options matching on name
54 | const userOverrideAttribute = tab.dataAttributes?.filter(
55 | (x) => x.name == attribute.name
56 | )[0];
57 |
58 | //If not found, push to tab
59 | if (!userOverrideAttribute) {
60 | tab.dataAttributes?.push({ ...attribute });
61 | }
62 | });
63 | }
64 | }
65 |
66 | static deleteTab(tab: FormTab, confirmDelete = false) {
67 | if (confirmDelete) {
68 | if (!confirm('Are you sure you want to delete this tab?')) {
69 | return;
70 | }
71 | }
72 |
73 | const definitions = get(mainDefinition);
74 | const filtered = definitions.filter((x) => x.tab != tab);
75 | mainDefinition.update(() => filtered);
76 |
77 | //Activate the first tab (assuming there is one left)
78 | if (filtered.length) {
79 | this.activateTab(filtered[0].tab?.tabOrder);
80 | }
81 |
82 | if (get(opts).builderAPIEvents?.onTabDeleted) {
83 | get(opts).builderAPIEvents?.onTabDeleted?.call(this, tab);
84 | }
85 | }
86 |
87 | static getTabElement(label: string) {
88 | return document.getElementById(`${LibraryPrefix}-tab-${label}`);
89 | }
90 |
91 | static activateTab(tabOrder: number | undefined) {
92 | const options = get(opts);
93 |
94 | if (options.activeTabOrderValue == tabOrder) {
95 | return;
96 | }
97 |
98 | options.activeTabOrderValue = tabOrder;
99 | opts.update(() => options);
100 |
101 | if (options.builderAPIEvents?.onTabChanged) {
102 | options.builderAPIEvents?.onTabChanged?.call(undefined, TabManager.getActiveTabDefinition());
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/lib/Tabs/TabPropertyPanel.svelte:
--------------------------------------------------------------------------------
1 |
40 |
41 |
42 |
43 |
52 |
53 |
54 |
55 |
56 | {#if selectedLink == 'General'}
57 |
58 |
59 |
60 |
61 |
62 |
63 | {/if}
64 |
65 | {#if tab.dataAttributes}
66 | {#each tab.dataAttributes as item}
67 | {#if !item.tab && selectedLink == 'General'}
68 |
69 | {:else if item.tab == selectedLink}
70 |
71 | {/if}
72 | {/each}
73 | {/if}
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/src/lib/Tools/BuildTools.svelte:
--------------------------------------------------------------------------------
1 |
60 |
61 | {#if !$opts.disabledBuildTools?.getData}
62 |
Show Definition
63 | {/if}
64 |
65 | {#if !$opts.disabledBuildTools?.validateDefinition}
66 |
Validate Definition
67 | {/if}
68 |
69 | {#if !$opts.disabledBuildTools?.exportDefinition}
70 |
{
73 | downloadDefinition();
74 | }}>Export Definition
76 | {/if}
77 |
78 | {#if !$opts.disabledBuildTools?.importDefinition}
79 | {#key files}
80 |
87 | {/key}
88 | {/if}
89 |
90 | {#if !$opts.disabledBuildTools?.clearCurrentTab}
91 |
{
94 | DefinitionManager.clearData(TabManager.getActiveTabDefinition().tab);
95 | }}>Clear Current Tab
97 | {/if}
98 |
99 | {#if !$opts.disabledBuildTools?.clearData}
100 |
{
103 | DefinitionManager.clearData();
104 | }}>Clear All Tabs
106 | {/if}
107 |
108 | {#if $opts.customBuildTools}
109 | {#each $opts.customBuildTools as tool}
110 |
{
113 | contentData = '';
114 | customContentData = tool.onClick.call(undefined, {});
115 | }}>{tool.buttonText}
117 | {/each}
118 | {/if}
119 |
120 |
137 |
--------------------------------------------------------------------------------
/src/lib/Tools/RenderTools.svelte:
--------------------------------------------------------------------------------
1 |
30 |
31 | {#if !$opts.disabledRenderTools?.serialize}
32 |
Serialize
33 | {/if}
34 |
35 | {#if !$opts.disabledRenderTools?.validateForm}
36 |
Validate
37 | {/if}
38 |
39 | {#if !$opts.disabledRenderTools?.resetForm}
40 |
{
43 | RenderAPI.resetForm();
44 | $mainDefinition = $mainDefinition; //Force redraw/sync
45 | }}>Reset
47 | {/if}
48 |
49 | {#if $opts.customRenderTools}
50 | {#each $opts.customRenderTools as tool}
51 |
{
54 | contentData = '';
55 | customContentData = tool.onClick.call(undefined, {});
56 | }}>{tool.buttonText}
58 | {/each}
59 | {/if}
60 |
--------------------------------------------------------------------------------
/src/lib/Tools/Tools.svelte:
--------------------------------------------------------------------------------
1 |
29 |
30 |
31 |
38 |
39 |
40 |
41 | {#if contentData}
42 | (contentData = '')} title="Clear">
43 |
44 |
45 |
46 | {
50 | navigator.clipboard.writeText(contentData);
51 |
52 | copyBlink = true;
53 | setTimeout(() => {
54 | copyBlink = false;
55 | }, 800);
56 | }}
57 | >
58 |
59 |
60 | {copyBlink ? 'Copied to Clipboard' : ''}
61 | {/if}
62 |
63 |
64 | {#key contentData}
65 |
66 | {contentData}
67 |
68 | {/key}
69 |
70 | {#key customContentData}
71 |
72 | {@html customContentData}
73 |
74 | {/key}
75 |
76 |
77 |
78 |
79 |
100 |
--------------------------------------------------------------------------------
/src/lib/Utils/ComponentUtilities/CheckboxRadioCommon.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 | {#if field.choiceConfiguration}
13 |
32 |
33 |
34 |
35 |
43 | {#if field.choiceConfiguration.checkIcon == undefined || field.choiceConfiguration.checkIcon == true}
44 |
45 |
46 |
47 | {/if}
48 |
49 |
{label}
50 |
51 |
52 | {/if}
53 |
54 |
66 |
--------------------------------------------------------------------------------
/src/lib/Utils/ComponentUtilities/ComponentLabel.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 | {#if field.labelAttributes}
10 |
11 |
17 | {#if $opts.allowHtmlLabels}
18 | {@html field.labelAttributes.label}
19 | {:else}
20 | {field.labelAttributes.label}
21 | {/if}
22 |
23 | {#if field.tooltipAttributes?.text}
24 |
25 |
32 | {/if}
33 |
34 | {/if}
35 |
36 |
42 |
--------------------------------------------------------------------------------
/src/lib/Utils/ComponentUtilities/GroupSlot.svelte:
--------------------------------------------------------------------------------
1 |
20 |
21 | {#if !field || !field.hidden}
22 |
23 |
28 | {/if}
29 |
30 |
36 |
--------------------------------------------------------------------------------
/src/lib/Utils/ComponentUtilities/SelectOptions.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 | {#if field.choiceConfiguration?.initialBlankChoice}
8 |
9 | {/if}
10 |
11 | {#if field.choiceConfiguration && field.choiceConfiguration.choices}
12 | {#each field.choiceConfiguration.choices as choice}
13 | {#if choice.label}
14 |
{choice.label}
15 | {/if}
16 | {/each}
17 | {/if}
18 |
--------------------------------------------------------------------------------
/src/lib/Utils/Misc/Theme.ts:
--------------------------------------------------------------------------------
1 | import type { StyleConfig } from '$lib/Utils/types';
2 |
3 | export const Theme = ['Default', 'Dark'] as const;
4 | export type ThemeType = typeof Theme[number];
5 |
6 | export const ThemeMap: Record
= {
7 | Default: {
8 | root: {
9 | css: {
10 | color: 'black',
11 | background: 'white'
12 | },
13 | cssDropDownMenu: {
14 | backgroundColor: '#eee'
15 | },
16 | checkboxColor: 'primary',
17 | toolButtonBackgroundColor: '#fafbfc',
18 | toolButtonColor: '#24292e',
19 | toolButtonHoverBackgroundColor: '#f3f4f6'
20 | },
21 | header: {
22 | css: {
23 | background: '#8AAAE5'
24 | }
25 | },
26 | propertyPanel: {
27 | propertyPanelHeaderBackground: '#E5C58A',
28 | propertyPanelBackground: '#B7CCF4'
29 | },
30 | form: {
31 | pointerOverComponentBorder: '1px solid #00a4bd',
32 | dragNDropHoverBackgroundColor: 'yellow',
33 | emptyFormTextColor: 'black',
34 | emptyFormMinHeight: '40vh',
35 | cssDragNDropLeftRight: {
36 | border: '1px dashed #0d99f2',
37 | backgroundColor: '#e5f5f8',
38 | borderRadius: '5px',
39 | height: '32px',
40 | margin: '10px',
41 | marginBottom: '10px'
42 | },
43 | cssDragNDropTopBottom: {
44 | border: '1px dashed #0d99f2',
45 | backgroundColor: '#eaeff0',
46 | borderRadius: '5px',
47 | height: '16px',
48 | margin: '10px'
49 | }
50 | },
51 | componentSelection: {
52 | css: {
53 | maxHeight: '50vh',
54 | background: 'white',
55 | border: '2px solid black'
56 | },
57 | utilityMenuHoverColor: 'purple',
58 | componentItemHoverBackgroundColor: '#E8EFFD',
59 | minimizedBorder: '4px solid red'
60 | },
61 | tab: {
62 | activeTabColor: 'black',
63 | activeTabBackgroundColor: 'white'
64 | }
65 | },
66 | Dark: {
67 | root: {
68 | css: {
69 | color: '#fff',
70 | background: '#000C1D'
71 | },
72 | cssDropDownMenu: {
73 | backgroundColor: '#122d42'
74 | },
75 | checkboxColor: 'primary',
76 | toolButtonBackgroundColor: '#122d42',
77 | toolButtonColor: '#ffffffcc',
78 | toolButtonHoverBackgroundColor: '#7e57c2'
79 | },
80 | header: {
81 | css: {
82 | background: '#282c34'
83 | }
84 | },
85 | propertyPanel: {
86 | propertyPanelHeaderBackground: '#5f7e97',
87 | propertyPanelBackground: '#000C1D'
88 | },
89 | form: {
90 | pointerOverComponentBorder: '1px solid #ffffff',
91 | dragNDropHoverBackgroundColor: '#5f7e97',
92 | emptyFormTextColor: '#fff',
93 | emptyFormMinHeight: '40vh',
94 | cssDragNDropLeftRight: {
95 | border: '1px dashed #5f7e9779',
96 | backgroundColor: '#5f7e97',
97 | borderRadius: '5px',
98 | height: '32px',
99 | margin: '10px',
100 | marginBottom: '10px'
101 | },
102 | cssDragNDropTopBottom: {
103 | border: '1px dashed #5f7e9779',
104 | backgroundColor: '#122d42',
105 | borderRadius: '5px',
106 | height: '16px',
107 | margin: '10px'
108 | }
109 | },
110 | componentSelection: {
111 | css: {
112 | maxHeight: '50vh',
113 | background: '#122d42',
114 | border: '2px solid #7e57c2cc'
115 | },
116 | utilityMenuHoverColor: '#7e57c2',
117 | componentItemHoverBackgroundColor: '#7e57c2',
118 | minimizedBorder: '4px solid #7e57c2'
119 | },
120 | tab: {
121 | activeTabColor: '#fff',
122 | activeTabBackgroundColor: '#7e57c2cc'
123 | }
124 | }
125 | };
126 |
--------------------------------------------------------------------------------
/src/lib/Utils/Misc/flavor.ts:
--------------------------------------------------------------------------------
1 | import type { FormComponentsType } from '$lib/Utils/types';
2 |
3 | export const flavor: 'community' | 'pro' | 'enterprise' = 'community';
4 |
5 | export const DynamicImportMap: Partial>> = {
6 | Button: import(`../../Components/Button.svelte`),
7 | Text: import(`../../Components/Text.svelte`),
8 | Select: import(`../../Components/Select.svelte`),
9 | Number: import(`../../Components/Number.svelte`),
10 | Hidden: import(`../../Components/Hidden.svelte`),
11 | 'Text Area': import(`../../Components/Text Area.svelte`),
12 | 'Checkbox Group': import(`../../Components/Checkbox Group.svelte`),
13 | 'Radio Group': import(`../../Components/Radio Group.svelte`),
14 | Date: import(`../../Components/Date.svelte`),
15 | Header: import(`../../Components/Header.svelte`),
16 | Paragraph: import(`../../Components/Paragraph.svelte`),
17 | Password: import(`../../Components/Password.svelte`),
18 | Color: import(`../../Components/Color.svelte`),
19 | 'File Upload': import(`../../Components/File Upload.svelte`),
20 | Progress: import(`../../Components/Progress.svelte`),
21 | Meter: import(`../../Components/Meter.svelte`),
22 | Audio: import(`../../Components/Audio.svelte`),
23 | Video: import(`../../Components/Video.svelte`),
24 | Range: import(`../../Components/Range.svelte`),
25 | DateTime: import(`../../Components/DateTime.svelte`),
26 | Month: import(`../../Components/Month.svelte`),
27 | Week: import(`../../Components/Week.svelte`),
28 | Time: import(`../../Components/Time.svelte`),
29 | Link: import(`../../Components/Link.svelte`),
30 | Picture: import(`../../Components/Picture.svelte`),
31 | Canvas: import(`../../Components/Canvas.svelte`),
32 | Divider: import(`../../Components/Divider.svelte`),
33 | Stars: import(`../../Components/Stars.svelte`)
34 | };
35 |
--------------------------------------------------------------------------------
/src/lib/Utils/MiscComponents/DisplayContentsWrapper.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
14 |
--------------------------------------------------------------------------------
/src/lib/Utils/MiscComponents/DragImage.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/lib/Utils/MiscComponents/DropdownMenu.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 | (menuOpen = false)}>
11 | (menuOpen = !menuOpen)}>
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
40 |
--------------------------------------------------------------------------------
/src/lib/Utils/MiscComponents/Loader.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
38 |
--------------------------------------------------------------------------------
/src/lib/Utils/MiscComponents/ScrollToElementFork/helper.ts:
--------------------------------------------------------------------------------
1 | export interface ScrollToElementOptions {
2 | offset?: number | Function;
3 | duration?: number;
4 | delay?: number | boolean;
5 | easing?: any;
6 | x?: number;
7 | y?: number;
8 | scrollX?: boolean;
9 | scrollY?: boolean;
10 | onStart?: any;
11 | onDone?: any;
12 | container?: any;
13 | onAborting?: any;
14 | element?: HTMLElement|null;
15 | }
16 |
17 | export interface ScrollToElementPosition {
18 | top: number;
19 | left: number;
20 | }
21 |
22 | export const selector = (selector: HTMLElement|null|undefined): HTMLElement | null |undefined => {
23 | if (typeof selector === 'string') {
24 | return document.querySelector(selector);
25 | }
26 |
27 | return selector;
28 | };
29 |
30 | export const extend = (...args: ScrollToElementOptions[]): ScrollToElementOptions =>
31 | Object.assign({}, ...args);
32 |
33 | export const cumulativeOffset = (element: HTMLElement | any): ScrollToElementPosition => {
34 | let top = 0;
35 | let left = 0;
36 |
37 | do {
38 | top += element.offsetTop || 0;
39 | left += element.offsetLeft || 0;
40 | element = element.offsetParent;
41 | } while (element);
42 |
43 | return {
44 | top,
45 | left
46 | };
47 | };
48 |
49 | export const directScroll = (element: HTMLElement | any): boolean =>
50 | element && element !== document && element !== document.body;
51 |
52 | export const scrollTop = (element: HTMLElement | any, value?: number): number => {
53 | const inSetter = value !== undefined;
54 | if (directScroll(element)) {
55 | return inSetter ? (element.scrollTop = value) : element.scrollTop;
56 | }
57 | return inSetter
58 | ? (document.documentElement.scrollTop = document.body.scrollTop = value)
59 | : window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
60 | };
61 |
62 | export const scrollLeft = (element: HTMLElement, value?: number): number => {
63 | const inSetter = value !== undefined;
64 | if (directScroll(element)) {
65 | return inSetter ? (element.scrollLeft = value) : element.scrollLeft;
66 | }
67 | return inSetter
68 | ? (document.documentElement.scrollLeft = document.body.scrollLeft = value)
69 | : window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0;
70 | };
--------------------------------------------------------------------------------
/src/lib/Utils/MiscComponents/ScrollToElementFork/service.ts:
--------------------------------------------------------------------------------
1 | import {
2 | type ScrollToElementOptions,
3 | cumulativeOffset,
4 | scrollLeft,
5 | scrollTop,
6 | extend,
7 | selector
8 | } from '$lib/Utils/MiscComponents/ScrollToElementFork/helper';
9 | import { cubicInOut } from 'svelte/easing';
10 | import { loop, noop, now } from 'svelte/internal';
11 |
12 | const defaultOptions: ScrollToElementOptions = {
13 | container: 'body',
14 | duration: 500,
15 | delay: 0,
16 | offset: 0,
17 | easing: cubicInOut,
18 | onStart: noop,
19 | onDone: noop,
20 | onAborting: noop,
21 | scrollX: false,
22 | scrollY: true
23 | };
24 |
25 | const scrollToInternal = (options: ScrollToElementOptions): (() => void) => {
26 | const {
27 | duration,
28 | delay,
29 | easing,
30 | x = 0,
31 | y = 0,
32 | scrollX,
33 | scrollY,
34 | onStart,
35 | onDone,
36 | container,
37 | onAborting,
38 | element
39 | } = options;
40 |
41 | let { offset } = options;
42 |
43 | if (typeof offset === 'function') {
44 | offset = offset() as Function;
45 | }
46 |
47 | const cumulativeOffsetContainer = cumulativeOffset(container);
48 | const cumulativeOffsetTarget = element ? cumulativeOffset(element) : { top: y, left: x };
49 |
50 | const initialX = scrollLeft(container);
51 | const initialY = scrollTop(container);
52 |
53 | const targetX = cumulativeOffsetTarget.left - cumulativeOffsetContainer.left + (offset as number);
54 | const targetY = cumulativeOffsetTarget.top - cumulativeOffsetContainer.top + (offset as number);
55 |
56 | const diffX = targetX - initialX;
57 | const diffY = targetY - initialY;
58 |
59 | let scrolling = true;
60 | let started = false;
61 | const startTime = now() + delay;
62 | const endTime = startTime + duration;
63 |
64 | function scrollToTopLeft(element: HTMLElement, top: number, left: number): void {
65 | if (scrollX) scrollLeft(element, left);
66 | if (scrollY) scrollTop(element, top);
67 | }
68 |
69 | function start(delayStart: number | boolean): void {
70 | if (!delayStart) {
71 | started = true;
72 | onStart(element, { x, y });
73 | }
74 | }
75 |
76 | function tick(progress: number): void {
77 | scrollToTopLeft(container, initialY + diffY * progress, initialX + diffX * progress);
78 | }
79 |
80 | function stop(): void {
81 | scrolling = false;
82 | }
83 |
84 | loop((now): boolean => {
85 | if (!started && now >= startTime) {
86 | start(false);
87 | }
88 |
89 | if (started && now >= endTime) {
90 | tick(1);
91 | stop();
92 | onDone(element, { x, y });
93 | }
94 |
95 | if (!scrolling) {
96 | onAborting(element, { x, y });
97 | return false;
98 | }
99 | if (started && duration) {
100 | const p = now - startTime;
101 | const t = 0 + 1 * easing(p / duration);
102 | tick(t);
103 | }
104 |
105 | return true;
106 | });
107 |
108 | start(delay);
109 |
110 | tick(0);
111 |
112 | return stop;
113 | };
114 |
115 | const proceedOptions = (options: ScrollToElementOptions): ScrollToElementOptions => {
116 | const opts = extend({}, defaultOptions, options);
117 | opts.container = selector(opts.container);
118 | opts.element = selector(opts.element);
119 | return opts;
120 | };
121 |
122 | const scrollContainerHeight = (containerElement: HTMLElement | Document): number => {
123 | if (containerElement && containerElement !== document && containerElement !== document.body) {
124 | return (
125 | (containerElement as HTMLElement).scrollHeight -
126 | (containerElement as HTMLElement).offsetHeight
127 | );
128 | }
129 | const { body } = document;
130 | const html = document.documentElement;
131 |
132 | return Math.max(
133 | body.scrollHeight,
134 | body.offsetHeight,
135 | html.clientHeight,
136 | html.scrollHeight,
137 | html.offsetHeight
138 | );
139 | };
140 |
141 | const setGlobalOptions = (options: ScrollToElementOptions): void => {
142 | extend(defaultOptions, options || {});
143 | };
144 |
145 | const scrollTo = (options: ScrollToElementOptions): (() => void) =>
146 | scrollToInternal(proceedOptions(options));
147 |
148 | const scrollToBottom = (options?: ScrollToElementOptions): (() => void) => {
149 | options = proceedOptions(options);
150 |
151 | return scrollToInternal(
152 | extend(options, {
153 | element: null,
154 | y: scrollContainerHeight(options.container)
155 | })
156 | );
157 | };
158 |
159 | const scrollToTop = (options?: ScrollToElementOptions): (() => void) => {
160 | options = proceedOptions(options);
161 |
162 | return scrollToInternal(
163 | extend(options, {
164 | element: null,
165 | y: 0
166 | })
167 | );
168 | };
169 |
170 | const makeScrollToAction =
171 | (scrollToFunc: Function) => (node: Node, options: ScrollToElementOptions) => {
172 | let current = options;
173 | const handle: EventListenerOrEventListenerObject = (e: Event) => {
174 | e.preventDefault();
175 | scrollToFunc(typeof current === 'string' ? { element: current } : current);
176 | };
177 | node.addEventListener('click', handle);
178 | node.addEventListener('touchstart', handle);
179 | return {
180 | update(options: ScrollToElementOptions): void {
181 | current = options;
182 | },
183 | destroy(): void {
184 | node.removeEventListener('click', handle);
185 | node.removeEventListener('touchstart', handle);
186 | }
187 | };
188 | };
189 |
190 | // Actions
191 | export const scrollto = makeScrollToAction(scrollTo);
192 | export const scrolltotop = makeScrollToAction(scrollToTop);
193 | export const scrolltobottom = makeScrollToAction(scrollToBottom);
194 |
195 | // Methods
196 | export const animateScroll = { scrollTo, scrollToTop, scrollToBottom, setGlobalOptions };
197 |
--------------------------------------------------------------------------------
/src/lib/Utils/MiscComponents/StarFork/Star.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
16 | {#if fillPercentage < 1 && fillPercentage > 0}
17 |
18 |
19 |
20 |
24 |
25 |
26 |
27 | {/if}
28 |
38 |
39 |
--------------------------------------------------------------------------------
/src/lib/Utils/MiscComponents/StarFork/StarRating2.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 | {#each Array(config.countStars) as star, id}
22 | {#if parseInt(config.score) == id}
23 |
29 | {:else if parseInt(config.score) > id}
30 |
31 | {:else}
32 |
33 | {/if}
34 | {/each}
35 |
36 |
37 |
47 |
48 |
49 | {#if config.showScore}
50 |
51 | ({parseFloat((config.score / config.countStars) * 100).toFixed(2)}%)
52 |
53 | {/if}
54 |
55 |
56 |
88 |
--------------------------------------------------------------------------------
/src/lib/Utils/MiscComponents/StyledSidePanel.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
20 |
21 |
22 |
23 |
69 |
--------------------------------------------------------------------------------
/src/lib/Utils/Utils.ts:
--------------------------------------------------------------------------------
1 | import { animateScroll } from '$lib/Utils/MiscComponents/ScrollToElementFork/service';
2 | import type { CustomDataAttribute, Field } from '$lib/Utils/types';
3 |
4 | export function capitalizeFirstLetter(string: string) {
5 | return string[0].toUpperCase() + string.slice(1);
6 | }
7 |
8 | //Convert CustomDataAttribute[] to dynamic object array with the data-* attributes
9 | export function convertDataAttributes(attributes: CustomDataAttribute[] | undefined) {
10 | const results: any = {};
11 |
12 | if (!attributes) {
13 | return results;
14 | }
15 |
16 | if (!Array.isArray(attributes)) {
17 | return attributes;
18 | }
19 |
20 | attributes.forEach((attribute) => {
21 | const name = `data-${attribute.name}`;
22 |
23 | if (
24 | typeof attribute.value === 'boolean' ||
25 | typeof attribute.value === 'number' ||
26 | typeof attribute.value === 'string'
27 | ) {
28 | results[name] = attribute.value;
29 | }
30 |
31 | if (typeof attribute.value === 'object') {
32 | results[name] = JSON.stringify(attribute.value);
33 | }
34 | });
35 |
36 | return results;
37 | }
38 |
39 | //Scroll to element and offset by half the viewport height
40 | export function scrollTo(field: Field) {
41 | const offset = (-1 * window.innerHeight) / 2;
42 | const id = field.htmlAttributes.id;
43 | const standardElement = document.getElementById(id);
44 |
45 | //First try finding an element that got a standard id
46 | if (standardElement) {
47 | animateScroll.scrollTo({ element: `#${id}`, offset: offset });
48 | return;
49 | }
50 |
51 | //Some elements don't have a standard id (like checkbox group), so next try to scroll to its label element
52 | const labelEl = document.querySelector(`[for="${id}"]`);
53 | if (labelEl) {
54 | animateScroll.scrollTo({ element: labelEl, offset: offset });
55 | return;
56 | }
57 | }
58 |
59 | export function CScope(suffix: string) {
60 | return `${LibraryPrefix}-${suffix}`;
61 | }
62 | export function RemoveAlphaNumeric(value: string | undefined) {
63 | if (!value) {
64 | return '';
65 | }
66 |
67 | return value.replace(/[\W_]+/g, ' ').trim();
68 | }
69 |
70 | export const LibraryPrefix = 'svelte-fb';
71 |
--------------------------------------------------------------------------------
/src/lib/Utils/other-types/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/src/lib/Utils/store.ts:
--------------------------------------------------------------------------------
1 | import type { ConditionManager } from '$lib/lib/ConditionManager';
2 | import type { OptionsProcessor } from '$lib/lib/OptionsProcessor';
3 | import { TabManager } from '$lib/Tabs/TabManager';
4 | import type {
5 | ComponentImport,
6 | BuilderOptions,
7 | SvelteFBComponent,
8 | FormDefinition,
9 | Field,
10 | views
11 | } from '$lib/Utils/types';
12 | import { writable, derived } from 'svelte/store';
13 |
14 | export const importedComponents = writable([] as ComponentImport[]);
15 |
16 | export const mainDefinition = writable({} as FormDefinition[]);
17 |
18 | export const opts = writable({} as BuilderOptions);
19 |
20 | export const isDraggingStore = writable(false);
21 | export const dropTargetRowIndexStore = writable(0);
22 | export const isPointerOverFieldStore = writable(false);
23 | export const focusedField = writable({} as Field);
24 | export const propertyField = writable({} as Field);
25 | export const showPropertyPanel = writable(false);
26 | export const tabPropertiesOpen = writable(false);
27 |
28 | export const componentSelectionStyle = writable({} as CSSStyleDeclaration);
29 | export const componentSelectionPoppedOut = writable(false);
30 |
31 | export const isTabDragging = writable(false);
32 |
33 | export const isComponentSelectionDragging = writable(false);
34 | export const isComponentDragging = writable(false);
35 | export const setComponentSelectionCategory = writable('');
36 |
37 | export const view = writable('build' as views);
38 |
39 | export const rendering = derived(view, ($view) => $view == 'render' || $view == 'preview');
40 |
41 | export const componentsLoaded = writable(false);
42 |
43 | export const componentInstances = writable({} as Record);
44 |
45 | export const optionsProcessorStore = writable({} as OptionsProcessor);
46 |
47 | export const formMounted = writable(false);
48 |
49 | export const allFields = writable([] as Field[]);
50 | export const numFormsMounted = writable(0);
51 |
52 | export const allFormsMounted = derived(
53 | numFormsMounted,
54 | ($numFormsMounted) => $numFormsMounted == TabManager.numTabs()
55 | );
56 |
57 | export const conditionManager = writable({} as ConditionManager);
58 | export const reloadConditions = writable(false);
59 |
--------------------------------------------------------------------------------
/src/lib/Views/Conditions.svelte:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pragmatic-engineering/svelte-form-builder-community/3c0b9b916ae5ab9c5a46154913b6a19612ab27ea/src/lib/Views/Conditions.svelte
--------------------------------------------------------------------------------
/src/lib/Views/Form.svelte:
--------------------------------------------------------------------------------
1 |
79 |
80 | {#if $view == 'build' || $rendering}
81 |
82 | {#if rows.length > 0}
83 | {#each [...rows] as row, rowIndex}
84 |
85 | {#if DnD.showTopBottomDropzone(isDragging, dropTargetRowIndex, rowIndex, $isComponentSelectionDragging)}
86 |
DnD.dropToNewRow(e, rowIndex)} />
87 | {/if}
88 |
89 |
90 |
96 | {#each row.fields as field, fieldIndex (field.htmlAttributes.id)}
97 | {@const componentImport = DefinitionManager.getFieldComponent(field)}
98 | {@const dummyConst = syncDefaultValue(field)}
99 | {#if componentImport}
100 |
101 | {#if DnD.showLeftDropzone(isDragging, dropTargetRowIndex, rowIndex, row, fieldIndex, $isComponentSelectionDragging)}
102 |
DnD.dropToExistingRow(e, rowIndex, fieldIndex)}
104 | />
105 | {/if}
106 |
107 |
108 |
116 | $view == 'build' && !$isDraggingStore && QuickMenuUtils.pointerOver(e, field)}
117 | on:pointerleave={(e) =>
118 | !pointerInsideComponent && QuickMenuUtils.pointerLeave(e, field)}
119 | on:dragstart={(e) =>
120 | $view == 'build' && DnD.componentDragStart(e, field, rowIndex, fieldIndex)}
121 | on:dragenter={(e) =>
122 | $view == 'build' && DnD.componentDragEnter(e, field, rowIndex)}
123 | on:dragover={(e) => $view == 'build' && DnD.componentDragOver(e, field)}
124 | on:dragend={(e) => $view == 'build' && DnD.componentDragEnd(e, field)}
125 | >
126 |
127 |
128 |
137 |
138 |
139 |
148 |
149 |
150 |
151 |
152 | {#if DnD.showRightDropzone(isDragging, dropTargetRowIndex, rowIndex, row, fieldIndex, $isComponentSelectionDragging)}
153 | DnD.dropToExistingRow(e, rowIndex, fieldIndex + 1)}
155 | />
156 | {/if}
157 | {:else}
158 |
159 | A Component with name {field.componentName} was not provided -- Check the
160 | imports provided in the componentOptions prop & case sensitivity of componentName
161 |
162 | {/if}
163 | {/each}
164 |
165 |
166 |
167 |
168 | {#if DnD.showTopBottomDropzone(isDragging, dropTargetRowIndex, rowIndex, $isComponentSelectionDragging)}
169 | DnD.dropToNewRow(e, rowIndex + 1)} />
170 | {/if}
171 | {/each}
172 | {:else if $view == 'build'}
173 | DnD.dropToNewRow(e, 0)}
175 | height="90%"
176 | text="Click or Drag a field to this area"
177 | />
178 | {/if}
179 |
180 | {/if}
181 |
182 |
220 |
--------------------------------------------------------------------------------
/src/lib/Views/Header.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
19 |
20 |
45 |
46 |
88 |
--------------------------------------------------------------------------------
/src/lib/Views/Settings.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
21 |
27 |
33 |
39 |
45 |
51 |
57 |
63 |
69 |
75 |
76 |
77 | {@const selectID = `${LibraryPrefix}-theme-${nanoid(10)}`}
78 |
79 | ($opts.styling = ThemeMap[selectedTheme])}
84 | >
85 | {#each Theme as theme}
86 | {theme}
87 | {/each}
88 |
89 |
90 |
91 |
92 |
101 |
--------------------------------------------------------------------------------
/src/lib/assets/svelte.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pragmatic-engineering/svelte-form-builder-community/3c0b9b916ae5ab9c5a46154913b6a19612ab27ea/src/lib/assets/svelte.png
--------------------------------------------------------------------------------
/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | // Re-export your entry components here
2 | import FormBuilder from '$lib/FormBuilder.svelte';
3 | import { DefinitionManager } from '$lib/lib/DefinitionManager';
4 | import { convertDataAttributes } from '$lib/Utils/Utils';
5 | import ComponentLabel from '$lib/Utils/ComponentUtilities/ComponentLabel.svelte';
6 | import GroupSlot from '$lib/Utils/ComponentUtilities/GroupSlot.svelte';
7 | import type { ThemeMap } from '$lib/Utils/Misc/Theme';
8 | import type { FormComponents, FormComponentsType } from '$lib/Utils/types';
9 | import type { BuilderAPI } from '$lib/lib/API/BuilderAPI';
10 | import type { RenderAPI } from '$lib/lib/API/RenderAPI';
11 |
12 | export {
13 | FormBuilder,
14 | GroupSlot,
15 | ComponentLabel,
16 | DefinitionManager,
17 | convertDataAttributes,
18 | ThemeMap,
19 | FormComponentsType,
20 | FormComponents,
21 | BuilderAPI,
22 | RenderAPI
23 | };
24 |
--------------------------------------------------------------------------------
/src/lib/lib/API/BuilderAPI.ts:
--------------------------------------------------------------------------------
1 | import { DefinitionManager } from '$lib/lib/DefinitionManager';
2 | import type { Field, FormTab } from '$lib/Utils/types';
3 | import type { FieldGroup } from '$lib/Utils/types';
4 | import { validateDefinitions } from '$lib/lib/Validation';
5 | import { TabManager } from '$lib/Tabs/TabManager';
6 | import { setComponentSelectionCategory } from '$lib/Utils/store';
7 |
8 | export class BuilderAPI {
9 | /**
10 | * Get the form definition
11 | * @param space
12 | * @returns JSON
13 | */
14 | static getDefinitionData(space: number | string | undefined = 2) {
15 | return DefinitionManager.getData(space);
16 | }
17 |
18 | /**
19 | * Return the Component Information for a field
20 | * @param field
21 | * @returns
22 | */
23 | static getFieldComponent(field: Field) {
24 | return DefinitionManager.getFieldComponent(field);
25 | }
26 |
27 | /**
28 | * Moves an existing field to a new row inside the same tab
29 | * @param tabID Tab ID
30 | * @param field
31 | * @param newRowAtIndex Index of the new Row
32 | * @returns
33 | */
34 | static moveFieldToNewRow(tabID: string, field: Field, newRowAtIndex: number) {
35 | return DefinitionManager.moveFieldToNewRow(tabID, field, newRowAtIndex);
36 | }
37 |
38 | /**
39 | * Moves a field to an existing row inside the same tab
40 | * @param tabID Tab ID
41 | * @param field
42 | * @param newRowAtIndex Index of the new Row
43 | * @param fieldIndex Optionally, specify the location of where inside the row the new field should go(if multiple fields in the row)
44 | * @returns
45 | */
46 | public static moveFieldToExistingRow(
47 | tabID: string,
48 | field: Field,
49 | rowIndex: number,
50 | fieldIndex: number | undefined
51 | ) {
52 | return DefinitionManager.moveFieldToExistingRow(tabID, field, rowIndex, fieldIndex);
53 | }
54 |
55 | /**
56 | * Duplicate a field
57 | * @param tabID
58 | * @param field
59 | * @returns
60 | */
61 | static copyField(tabID: string, field: Field) {
62 | return DefinitionManager.copyField(tabID, field);
63 | }
64 |
65 | /**
66 | * Add a field to a Tab
67 | * @param tabID
68 | * @param field
69 | * @param rowIndex
70 | * @param fieldIndex Add to a specific column of a specific row
71 | * @returns
72 | */
73 | static addFieldToTab(
74 | tabID: string,
75 | field: Field,
76 | rowIndex: number | undefined = undefined,
77 | fieldIndex: number | undefined = undefined
78 | ) {
79 | return DefinitionManager.addFieldToTab(tabID, field, rowIndex, fieldIndex);
80 | }
81 |
82 | /**
83 | * Add a group of fields
84 | * @param tabID
85 | * @param fieldGroup
86 | * @returns
87 | */
88 | static addFieldGroupToTab(tabID: string, fieldGroup: FieldGroup[]) {
89 | return DefinitionManager.addFieldGroupToTab(tabID, fieldGroup);
90 | }
91 |
92 | /**
93 | * Delete a field
94 | * @param tabID
95 | * @param field
96 | * @param confirmDelete
97 | * @returns
98 | */
99 | static deleteField(tabID: string, field: Field, confirmDelete = false) {
100 | return DefinitionManager.deleteField(tabID, field, confirmDelete);
101 | }
102 |
103 | /**
104 | * Add a new Tab
105 | * @returns
106 | */
107 | static addTab() {
108 | return TabManager.addTab();
109 | }
110 |
111 | /**
112 | * Delete a Tab
113 | * @param tab
114 | * @param confirmDelete
115 | * @returns
116 | */
117 | static deleteTab(tab: FormTab, confirmDelete = false) {
118 | return TabManager.deleteTab(tab, confirmDelete);
119 | }
120 |
121 | /**
122 | *
123 | * @returns The definition for the active Tab
124 | */
125 | static getActiveTabDefinition() {
126 | return TabManager.getActiveTabDefinition();
127 | }
128 |
129 | /**
130 | * Check the Form Definition for any validation issues
131 | * @returns
132 | */
133 | static validate() {
134 | return validateDefinitions();
135 | }
136 |
137 | /**
138 | * Clears all the data or data for a given tab
139 | * @param tab
140 | * @returns
141 | */
142 | static clearData(tab?: FormTab) {
143 | return DefinitionManager.clearData(tab);
144 | }
145 |
146 | static setComponentSelectionCategory(category: string) {
147 | if (category) {
148 | setComponentSelectionCategory.set(category);
149 | }
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/lib/lib/API/RenderAPI.ts:
--------------------------------------------------------------------------------
1 | import { DefinitionManager } from '$lib/lib/DefinitionManager';
2 | import { RenderManager } from '$lib/lib/RenderManager';
3 | import { validateUserInputs } from '$lib/lib/Validation';
4 |
5 | export class RenderAPI {
6 | /**
7 | * Get the form data
8 | * @param space
9 | * @returns JSON
10 | */
11 | static getData() {
12 | return RenderManager.serialize();
13 | }
14 |
15 | /**
16 | * Check the rendered Form for any validation issues
17 | * @returns
18 | */
19 | static validate() {
20 | return validateUserInputs();
21 | }
22 |
23 | /**
24 | * Reset the form
25 | * @param tab
26 | * @returns
27 | */
28 | static resetForm() {
29 | RenderManager.applyDefaultValues();
30 |
31 | DefinitionManager.visitAllFieldInstances((fieldInfo, componentInstance) => {
32 | //If available, call any custom reset code
33 | if (componentInstance && componentInstance.customReset) {
34 | componentInstance.customReset();
35 | }
36 | });
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/lib/lib/ConditionManager.ts:
--------------------------------------------------------------------------------
1 | import type { Condition, Field } from '$lib/Utils/types';
2 |
3 | export class ConditionManager {
4 | conditions: Condition[] = [];
5 |
6 | constructor() {
7 | //enterprise
8 | }
9 |
10 | async initFields() {
11 | //enterprise
12 | }
13 |
14 | async EvaluateFieldValue(e: Event | undefined, field: Field, customValue: unknown = undefined) {
15 | //enterprise
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/lib/lib/OptionsProcessor.ts:
--------------------------------------------------------------------------------
1 | import {
2 | componentsLoaded,
3 | importedComponents,
4 | mainDefinition,
5 | opts,
6 | reloadConditions,
7 | view
8 | } from '$lib/Utils/store';
9 | import { get } from 'svelte/store';
10 | import { TabManager } from '$lib/Tabs/TabManager';
11 | import merge from 'lodash.merge';
12 |
13 | import { DefinitionManager } from '$lib/lib/DefinitionManager';
14 | import {
15 | defaultHTMLAttributes,
16 | defaultDataAttributes,
17 | defaultDisableStandardHTMLValidityCheck,
18 | defaultHasChoices,
19 | defaultNoTooltipProperties,
20 | defaultDisabledHTMLAttributes
21 | } from '$lib/lib/DefaultAttributeMap';
22 |
23 | import type { BuilderOptions, ComponentOptions } from '$lib/Utils/types';
24 | import { ThemeMap } from '$lib/Utils/Misc/Theme';
25 | import type { FormComponentsType, FormDefinition, FormTab } from '$lib/Utils/types';
26 | import { DynamicImportMap, flavor } from '$lib/Utils/Misc/flavor';
27 |
28 | export class OptionsProcessor {
29 | usedComponents: FormComponentsType[] = [];
30 | constructor(public opts: BuilderOptions) {}
31 |
32 | static loadDefaults(options: BuilderOptions) {
33 | const defaults: Partial = {
34 | activeTabOrderValue: 1,
35 | enableTabs: true,
36 | confirmRemoveField: true,
37 | confirmRemoveTab: true
38 | };
39 |
40 | //Set theme
41 | if (options.theme) {
42 | defaults.styling = ThemeMap[options.theme];
43 | }
44 |
45 | if (options.styling) {
46 | defaults.styling = options.styling;
47 | }
48 |
49 | if (!defaults.styling) {
50 | defaults.styling = ThemeMap['Default'];
51 | }
52 |
53 | if (!options.formDefinition) {
54 | options.formDefinition = [];
55 | }
56 |
57 | if (flavor != 'enterprise') {
58 | if (!options.disabledViews) {
59 | options.disabledViews = {};
60 | }
61 | options.disabledViews.conditions = true;
62 | }
63 |
64 | //options = Object.assign({}, defaults, options);
65 | options = merge({}, defaults, options);
66 |
67 | opts.set(options);
68 |
69 | //Change default view if passed in
70 | view.set(options.view ?? 'build');
71 | }
72 |
73 | async init() {
74 | if (get(componentsLoaded)) {
75 | console.log(`Don't reload me Svelte!`);
76 | return;
77 | }
78 |
79 | await this.InitialComponentLoad();
80 | await this.ProcessFormDefinition();
81 |
82 | componentsLoaded.set(true);
83 | }
84 |
85 | private async InitialComponentLoad() {
86 | for (const definition of this.opts.formDefinition) {
87 | for (const row of definition.rows) {
88 | for (const field of row.fields) {
89 | await this.ImportComponent(field.componentName);
90 | }
91 | }
92 | }
93 | }
94 |
95 | private IsComponentLoaded(componentName: string) {
96 | return get(importedComponents).some((x) => x.componentOptions.componentName == componentName);
97 | }
98 |
99 | public async ImportComponent(componentName: FormComponentsType) {
100 | if (this.IsComponentLoaded(componentName)) {
101 | return;
102 | }
103 |
104 | const componentOptions = this.LoadComponent(componentName);
105 |
106 | try {
107 | const obj = componentOptions.customImport
108 | ? await componentOptions.customImport
109 | : await DynamicImportMap[componentName];
110 |
111 | if (!obj || !obj.default) {
112 | console.error(`No component found for ${componentName}`);
113 | return;
114 | }
115 |
116 | this.MergeComponentAttributes(componentOptions);
117 |
118 | const componentImport = {
119 | component: obj.default,
120 | componentOptions: componentOptions
121 | };
122 |
123 | importedComponents.update((componentMap) => [...componentMap, componentImport]);
124 |
125 | if (this.opts.builderAPIEvents?.onComponentImported) {
126 | this.opts.builderAPIEvents?.onComponentImported.call(this, componentImport);
127 | }
128 | } catch (error) {
129 | console.error(error);
130 | }
131 | }
132 |
133 | //Merge any internal attributes into componentOptions
134 | private MergeComponentAttributes(componentOptions: ComponentOptions) {
135 | const internalHTMLAttributes = defaultHTMLAttributes[componentOptions.componentName];
136 | if (internalHTMLAttributes) {
137 | componentOptions.htmlAttributes = merge(
138 | {},
139 | internalHTMLAttributes,
140 | componentOptions.htmlAttributes
141 | );
142 | //componentOptions.htmlAttributes = Object.assign({}, obj.htmlAttributes, componentOptions.htmlAttributes);
143 | }
144 |
145 | const internalDataAttributes = defaultDataAttributes[componentOptions.componentName];
146 | if (internalDataAttributes) {
147 | if (!componentOptions.dataAttributes) {
148 | componentOptions.dataAttributes = [];
149 | }
150 |
151 | for (const attribute of internalDataAttributes) {
152 | //Overriding attributes that come from componentOptions matching on name
153 | const userOverrideAttribute = componentOptions.dataAttributes?.filter(
154 | (x) => x.name == attribute.name
155 | )[0];
156 | if (userOverrideAttribute) {
157 | // attribute = userOverrideAttribute;
158 | // componentOptions.dataAttributes = componentOptions.dataAttributes?.filter((x) => x != userOverrideAttribute);
159 | } else {
160 | //If not found, push to componentOptions
161 | componentOptions.dataAttributes?.push({ ...attribute });
162 | }
163 | }
164 | }
165 |
166 | //Map over some other special attributes
167 | componentOptions.disableStandardHTMLValidityCheck =
168 | componentOptions.disableStandardHTMLValidityCheck ??
169 | defaultDisableStandardHTMLValidityCheck[componentOptions.componentName];
170 |
171 | componentOptions.hasChoices =
172 | componentOptions.hasChoices ?? defaultHasChoices[componentOptions.componentName];
173 |
174 | componentOptions.noTooltipProperties =
175 | componentOptions.noTooltipProperties ??
176 | defaultNoTooltipProperties[componentOptions.componentName];
177 |
178 | //Combine disabled HTML Attributes
179 | if (componentOptions.disabledHtmlAttributes) {
180 | componentOptions.disabledHtmlAttributes = [
181 | ...componentOptions.disabledHtmlAttributes,
182 | ...defaultDisabledHTMLAttributes[componentOptions.componentName]
183 | ];
184 | } else {
185 | componentOptions.disabledHtmlAttributes =
186 | defaultDisabledHTMLAttributes[componentOptions.componentName];
187 | }
188 | }
189 |
190 | private LoadComponent(componentName: FormComponentsType): ComponentOptions {
191 | let componentOption: ComponentOptions = {
192 | componentName: componentName
193 | };
194 |
195 | //Pass in user defined options (or load user defined component) if found by matching Component Name
196 | const userDefinedComponentOptions = this.opts.componentOptions?.find(
197 | (x) => x.componentName == componentName
198 | );
199 |
200 | if (userDefinedComponentOptions) {
201 | componentOption = { ...componentOption, ...userDefinedComponentOptions };
202 | }
203 |
204 | this.opts.componentOptions?.push(componentOption);
205 |
206 | return componentOption;
207 | }
208 |
209 | // Instantiate provided components to test they conform to a required interface
210 | // private IsComponentValid(obj: any, importPath: string, testElement: HTMLElement | null) {
211 | // let valid = true;
212 | // try {
213 | // let testInstance = new obj.default({ target: testElement });
214 | // if (!testInstance.hasOwnProperty('field')) {
215 | // console.error(
216 | // `Component ${importPath} is not valid. Add 'export let field: Field' as a prop`
217 | // );
218 | // valid = false;
219 | // }
220 | // testInstance = null;
221 | // } catch (error) {
222 | // console.error(error);
223 | // console.error(`Component ${importPath} could not be instantiated.`);
224 | // valid = false;
225 | // }
226 |
227 | // return valid;
228 | // }
229 |
230 | private async ProcessFormDefinition() {
231 | //Setup default tab if starting with empty definition
232 | if (!this.opts.formDefinition?.length) {
233 | const definition: FormDefinition = { rows: [] };
234 | this.SetupDefaultTab(definition);
235 | this.opts.formDefinition?.push(definition);
236 | }
237 |
238 | for (const definitions of this.opts.formDefinition) {
239 | if (!this.opts.enableTabs && !definitions.tab) {
240 | this.SetupDefaultTab(definitions);
241 | }
242 |
243 | TabManager.MergeTabAttributes(definitions.tab);
244 |
245 | definitions.rows.forEach((row) => {
246 | row.fields.forEach((field) => {
247 | DefinitionManager.processField(field);
248 | });
249 | });
250 | }
251 |
252 | mainDefinition.set(this.opts.formDefinition);
253 | }
254 |
255 | public async ReLoadDefinition(definition: BuilderOptions) {
256 | get(opts).formDefinition = definition.formDefinition;
257 | get(opts).conditions = definition.conditions;
258 | if (definition.conditions) {
259 | reloadConditions.set(true);
260 | }
261 |
262 | await this.ProcessFormDefinition();
263 | }
264 |
265 | private SetupDefaultTab(definition: FormDefinition) {
266 | definition.tab = {} as FormTab;
267 | definition.tab.tabOrder = 1;
268 | definition.tab.label = 'Default';
269 | definition.tab.id = 'default';
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/src/lib/lib/RenderManager.ts:
--------------------------------------------------------------------------------
1 | import { DefinitionManager } from '$lib/lib/DefinitionManager';
2 | import { componentInstances } from '$lib/Utils/store';
3 | import type { Field, SerializeResult } from '$lib/Utils/types';
4 | import { get } from 'svelte/store';
5 |
6 | export class RenderManager {
7 | static applyDefaultValues() {
8 | DefinitionManager.visitAllFields((field) => {
9 | field.htmlAttributes.value = field.defaultValue;
10 | });
11 | }
12 |
13 | static async serialize() {
14 | const formData: SerializeResult[] = [];
15 |
16 | const instances = get(componentInstances);
17 | if (instances) {
18 | for (const id in instances) {
19 | const componentInstance = instances[id];
20 | if (!componentInstance) {
21 | continue;
22 | }
23 |
24 | const fieldInfo = DefinitionManager.getFieldInfo({ id: id });
25 | if (fieldInfo) {
26 | if (!fieldInfo.field.htmlAttributes.disabled && fieldInfo.field.htmlAttributes.name) {
27 | const result: Partial = {
28 | name: fieldInfo.field.htmlAttributes.name,
29 | componentName: fieldInfo.field.componentName
30 | };
31 |
32 | //Do any validation defined inside component customGetUserData function
33 | if (componentInstance && componentInstance.customGetUserData) {
34 | result.value = await componentInstance.customGetUserData();
35 | } else {
36 | result.value = fieldInfo.field.htmlAttributes.value;
37 | }
38 |
39 | formData.push(result as SerializeResult);
40 | }
41 | }
42 | }
43 | }
44 |
45 | return JSON.stringify(formData);
46 | }
47 |
48 | public static getFieldUserData(field: Field) {
49 | const component = DefinitionManager.getFieldInstance(field);
50 | let value = field.htmlAttributes.value;
51 |
52 | //If component has a customGetUserData, then use that instead
53 | if (component.customGetUserData) {
54 | value = component.customGetUserData();
55 | }
56 |
57 | return value;
58 | }
59 |
60 | public static setFieldUserData(field: Field, value: unknown) {
61 | const component = DefinitionManager.getFieldInstance(field);
62 |
63 | //If component has a custom way to set its data, use that, otherwise use default htmlAttribute.value
64 | if (component.customSetUserData) {
65 | component.customSetUserData(value);
66 | } else {
67 | field.htmlAttributes.value = value;
68 | component.$set({ field: field });
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/lib/lib/Validation.ts:
--------------------------------------------------------------------------------
1 | import { get } from 'svelte/store';
2 |
3 | import type { ValidationError, ValidationResult, FieldInfo } from '$lib/Utils/types';
4 | import { DefinitionManager } from '$lib/lib/DefinitionManager';
5 | import { componentInstances } from '$lib/Utils/store';
6 |
7 | export function validateDefinitions() {
8 | const results: ValidationError[] = [];
9 |
10 | DefinitionManager.visitAllFieldInstances((fieldInfo, componentInstance) => {
11 | //Do any validation defined in options
12 | if (fieldInfo?.componentImport?.componentOptions.validateDefinition) {
13 | const validationResult = fieldInfo?.componentImport?.componentOptions.validateDefinition.call(
14 | undefined,
15 | fieldInfo
16 | );
17 | appendResults(validationResult, results);
18 | }
19 |
20 | //Do any validation defined inside component validateDefinition function
21 | if (componentInstance && componentInstance.validateDefinition) {
22 | const validationResult = componentInstance.validateDefinition();
23 | appendResults(validationResult, results);
24 | }
25 | });
26 |
27 | return results;
28 | }
29 |
30 | export function validateUserInputs() {
31 | const results: ValidationError[] = [];
32 | const instances = get(componentInstances);
33 |
34 | for (const id in instances) {
35 | const componentInstance = instances[id];
36 | if (!componentInstance) {
37 | continue;
38 | }
39 |
40 | const fieldInfo = DefinitionManager.getFieldInfo({ id: id });
41 |
42 | //Do standard HTML validity check
43 | if (
44 | fieldInfo &&
45 | !fieldInfo?.componentImport.componentOptions.disableStandardHTMLValidityCheck
46 | ) {
47 | const validationResult = validateStandardHtmlAttributes(fieldInfo);
48 | appendResults(validationResult, results);
49 | }
50 |
51 | //Do any validation defined in options
52 | if (fieldInfo?.componentImport?.componentOptions.validateUserInput) {
53 | const validationResult = fieldInfo?.componentImport?.componentOptions.validateUserInput.call(
54 | undefined,
55 | fieldInfo
56 | );
57 | appendResults(validationResult, results);
58 | }
59 |
60 | //Do any validation defined inside component validateUserInput function
61 | if (componentInstance && componentInstance.validateUserInput) {
62 | const validationResult = componentInstance.validateUserInput();
63 | appendResults(validationResult, results);
64 | }
65 | }
66 |
67 | return results;
68 | }
69 |
70 | function appendResults(validationResult: ValidationResult, results: ValidationError[]) {
71 | if (validationResult) {
72 | if (Array.isArray(validationResult) && validationResult.length) {
73 | results.push(...validationResult);
74 | } else {
75 | results.push(validationResult as ValidationError);
76 | }
77 | }
78 | }
79 |
80 | export function validateStandardHtmlAttributes(fieldInfo: FieldInfo): ValidationError | undefined {
81 | const result: ValidationError = { field: fieldInfo.field, tab: fieldInfo.tab, errors: [] };
82 |
83 | if (fieldInfo.field.htmlAttributes.id) {
84 | const el = document.getElementById(fieldInfo.field.htmlAttributes.id);
85 | if (!el) {
86 | //If element doesn't exist it may be because it is hidden/disabled due to some condition
87 | return;
88 | }
89 |
90 | result.validity = (el as HTMLInputElement).validity;
91 |
92 | if (result.validity.valueMissing) {
93 | result.errors.push(getErrorMessage_Required(fieldInfo));
94 | }
95 |
96 | if (result.validity.badInput) {
97 | result.errors.push(getErrorMessage(fieldInfo, 'has bad input'));
98 | }
99 |
100 | if (result.validity.patternMismatch) {
101 | result.errors.push(getErrorMessage(fieldInfo, 'does not match the requested pattern'));
102 | }
103 |
104 | if (result.validity.rangeOverflow) {
105 | result.errors.push(getErrorMessage(fieldInfo, 'is over the max'));
106 | }
107 |
108 | if (result.validity.rangeUnderflow) {
109 | result.errors.push(getErrorMessage(fieldInfo, 'is below the min'));
110 | }
111 |
112 | if (result.validity.stepMismatch) {
113 | result.errors.push(getErrorMessage(fieldInfo, 'has step mismatch'));
114 | }
115 |
116 | if (result.validity.tooLong) {
117 | result.errors.push(getErrorMessage(fieldInfo, 'is too long'));
118 | }
119 |
120 | if (result.validity.tooShort) {
121 | result.errors.push(getErrorMessage(fieldInfo, 'is too short'));
122 | }
123 |
124 | if (result.validity.typeMismatch) {
125 | result.errors.push(getErrorMessage(fieldInfo, 'input not match the expected type'));
126 | }
127 | }
128 |
129 | if (result.errors.length == 0) {
130 | return;
131 | }
132 |
133 | return result;
134 | }
135 |
136 | function getIdentifier(fieldInfo: Partial) {
137 | if (fieldInfo.field) {
138 | return fieldInfo.field.labelAttributes?.label ?? fieldInfo.field.componentName;
139 | }
140 | }
141 |
142 | export function getErrorMessage(fieldInfo: Partial, message: string) {
143 | return `${getIdentifier(fieldInfo)} ${message}`;
144 | }
145 |
146 | export function getErrorMessage_Required(fieldInfo: Partial) {
147 | return `${getIdentifier(fieldInfo)} is required`;
148 | }
149 |
--------------------------------------------------------------------------------
/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pragmatic-engineering/svelte-form-builder-community/3c0b9b916ae5ab9c5a46154913b6a19612ab27ea/static/favicon.png
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-auto';
2 | import preprocess from 'svelte-preprocess';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | // Consult https://github.com/sveltejs/svelte-preprocess
7 | // for more information about preprocessors
8 | preprocess: preprocess(),
9 |
10 | kit: {
11 | adapter: adapter()
12 | }
13 | };
14 |
15 | export default config;
16 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true
12 | }
13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
14 | //
15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
16 | // from the referenced tsconfig.json - TypeScript does not merge them in
17 | }
18 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import type { UserConfig } from 'vite';
3 |
4 | const config: UserConfig = {
5 | plugins: [sveltekit()]
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------