@stubber/form-fields 1.2.3 → 1.4.0

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.
@@ -0,0 +1,117 @@
1
+ <script>import { acceptCompletion, CompletionContext } from "@codemirror/autocomplete";
2
+ import { javascript, javascriptLanguage } from "@codemirror/lang-javascript";
3
+ import { EditorState } from "@codemirror/state";
4
+ import { EditorView, keymap } from "@codemirror/view";
5
+ import { Label } from "@stubber/ui/label";
6
+ import { basicSetup } from "codemirror";
7
+ export let field;
8
+ let value = $field.data.base || "";
9
+ let params = $field.spec?.params || {};
10
+ let globals = params?.globals || {};
11
+ let editor_view;
12
+ $: state_key = $field.state?.state_key;
13
+ $: label = $field.spec?.title;
14
+ $: hide_label = $field.spec?.hide_label;
15
+ $: isValid = !$field.state?.validation || $field.state?.validation?.valid;
16
+ $: validationMessage = $field.state?.validation?.message;
17
+ const tab_accept_completion = keymap.of([
18
+ {
19
+ key: "Tab",
20
+ run: acceptCompletion
21
+ }
22
+ ]);
23
+ function keysFor(obj) {
24
+ return Object.keys(obj ?? {});
25
+ }
26
+ function globalCompletions(ctx) {
27
+ const m = ctx.matchBefore(/[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*\.?/);
28
+ if (!m && !ctx.explicit) return null;
29
+ const text = m?.text ?? "";
30
+ const trailingDot = text.endsWith(".");
31
+ const parts = (trailingDot ? text.slice(0, -1) : text).split(".").filter(Boolean);
32
+ let container = globals;
33
+ for (let i = 0; i < Math.max(0, parts.length - (trailingDot ? 0 : 1)); i++) {
34
+ container = container?.[parts[i]];
35
+ if (!container) break;
36
+ }
37
+ const last = trailingDot ? "" : parts[parts.length - 1] ?? "";
38
+ const from = ctx.pos - last.length;
39
+ return {
40
+ from,
41
+ options: keysFor(container).map((key) => ({
42
+ label: key,
43
+ type: "property"
44
+ // 👈 simplified
45
+ })),
46
+ validFor: /^\w*$/
47
+ // keep suggestions stable while typing
48
+ };
49
+ }
50
+ const stubber_completions = javascriptLanguage.data.of({
51
+ autocomplete: globalCompletions
52
+ });
53
+ function bind_codemirror(el) {
54
+ const min_height_editor = EditorView.theme({
55
+ ".cm-content, .cm-gutter": { minHeight: "200px" },
56
+ "&": { maxHeight: "300px" },
57
+ ".cm-scroller": { overflow: "auto" }
58
+ });
59
+ editor_view = new EditorView({
60
+ state: EditorState.create({
61
+ doc: value,
62
+ extensions: [
63
+ basicSetup,
64
+ javascript(),
65
+ stubber_completions,
66
+ tab_accept_completion,
67
+ min_height_editor,
68
+ update_listener
69
+ ]
70
+ }),
71
+ parent: el
72
+ });
73
+ }
74
+ const update_listener = EditorView.updateListener.of((update) => {
75
+ if (update.docChanged) {
76
+ const new_value = update.state.doc.toString();
77
+ if (new_value !== value) {
78
+ value = new_value;
79
+ }
80
+ }
81
+ });
82
+ $: sync_field_to_editor(value);
83
+ const sync_field_to_editor = (new_value) => {
84
+ if (editor_view && editor_view.state.doc.toString() !== new_value) {
85
+ editor_view.dispatch({
86
+ changes: {
87
+ from: 0,
88
+ to: editor_view.state.doc.length,
89
+ insert: new_value
90
+ }
91
+ });
92
+ }
93
+ if ($field.data.base !== new_value) {
94
+ $field.data.base = new_value;
95
+ }
96
+ };
97
+ $: sync_field_to_value($field.data.base);
98
+ const sync_field_to_value = (new_value) => {
99
+ if (new_value !== value) {
100
+ value = new_value;
101
+ }
102
+ };
103
+ </script>
104
+
105
+ <div class="flex flex-col w-full my-2">
106
+ <Label for="input_{state_key}" class="block py-2 {hide_label ? 'hidden' : ''}">
107
+ {label}
108
+ </Label>
109
+ <div class="relative rounded-md">
110
+ <div use:bind_codemirror />
111
+ </div>
112
+ {#if validationMessage}
113
+ <Label class="mt-1.5 {!isValid ? `text-danger-500` : `text-success-500`}"
114
+ >{validationMessage}</Label
115
+ >
116
+ {/if}
117
+ </div>
@@ -0,0 +1,18 @@
1
+ import { SvelteComponent } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ field: any;
5
+ };
6
+ events: {
7
+ [evt: string]: CustomEvent<any>;
8
+ };
9
+ slots: {};
10
+ exports?: {} | undefined;
11
+ bindings?: string | undefined;
12
+ };
13
+ export type CodeProps = typeof __propDef.props;
14
+ export type CodeEvents = typeof __propDef.events;
15
+ export type CodeSlots = typeof __propDef.slots;
16
+ export default class Code extends SvelteComponent<CodeProps, CodeEvents, CodeSlots> {
17
+ }
18
+ export {};
@@ -1,113 +1,77 @@
1
1
  <script>
2
- import { syncStoreToStore } from "../../utils/syncing";
3
- import { deepEqual } from "fast-equals";
4
- import _ from "lodash-es";
2
+ import { merge, startCase } from "lodash-es";
5
3
  import { onMount } from "svelte";
6
- import { writable } from "svelte/store";
7
4
 
8
5
  import { Checkbox } from "@stubber/ui/checkbox";
9
6
  import { Label } from "@stubber/ui/label";
10
7
 
11
8
  export let field;
12
9
 
13
- const internal = writable({
14
- checks: [],
15
- });
16
-
17
10
  $: state_key = $field.state?.state_key;
18
11
  $: label = $field.spec?.title;
19
12
  $: hide_label = $field.spec?.hide_label;
20
13
  $: isValid = !$field.state?.validation || $field.state?.validation?.valid;
21
14
  $: validationMessage = $field.state?.validation?.message;
22
- $: items = _.isArray($field.spec?.params?.options)
23
- ? $field.spec?.params?.options?.map((o, index) => {
24
- let { label, value } = o || {};
25
- let _value = value !== undefined ? value : label;
26
- let _label = label ?? value;
27
- let _key = `${state_key}${label}${index}`;
28
- return { _key, _label, _value };
29
- })
30
- : [];
15
+ $: options = $field.spec?.params?.options; // options is an object with a label property, value property (optional) and checked property
31
16
 
32
- onMount(() => {
33
- // set field values that aren't set yet
34
- let f = _.cloneDeep($field);
35
- let initial_value = f?.data?.base || [];
36
- let initial_data = {
37
- ...f?.data,
38
- base: initial_value,
39
- };
40
- let initial_state_internal = {
41
- ...f?.state?.internal,
42
- checks: items.map((i) =>
43
- Boolean(
44
- initial_value.find((iv) => deepEqual(iv.value, i._value) && deepEqual(iv.label, i._label))
45
- )
46
- ),
47
- };
48
- _.set(f, "state.internal", initial_state_internal);
49
- _.set(f, "data", initial_data);
50
- if (!deepEqual(f, $field)) $field = f;
17
+ // $: console.log("field", $field);
51
18
 
52
- syncStoreToStore(
53
- field,
54
- internal,
55
- (a, b) => {
56
- let _clone = _.cloneDeep(a.state?.internal) || {};
19
+ // $: console.log("options", options);
20
+ onMount(() => {
21
+ /// merge all options into data.base
22
+ field.update((f) => {
23
+ if (!f.data) {
24
+ f.data = {};
25
+ }
26
+ if (!f.data.base) {
27
+ f.data.base = {};
28
+ }
57
29
 
58
- // get parts from data
59
- _clone.checks = items.map((i) =>
60
- Boolean(
61
- a?.data?.base.find(
62
- (iv) => deepEqual(iv.value, i._value) && deepEqual(iv.label, i._label)
63
- )
64
- )
65
- );
30
+ const merged_base = merge({}, options, f.data.base);
31
+ // ensure all options have a checked property
66
32
 
67
- // set field state if changed
68
- if (!deepEqual(a?.state?.internal, _clone)) {
69
- $field.state.internal = _clone;
33
+ Object.entries(merged_base).forEach(([key, item]) => {
34
+ if (typeof item === "object" && item.checked === undefined) {
35
+ item.checked = false;
70
36
  }
37
+ });
71
38
 
72
- return _clone;
73
- },
74
- (a, b) => {
75
- let _clone = _.cloneDeep(a) || {};
76
- // update the state
77
- _.set(_clone, "state.internal", _.cloneDeep(b));
78
- // update the data
79
- let base = [];
80
- b.checks.forEach((c, index) => {
81
- if (c)
82
- base.push({
83
- value: items[index]._value,
84
- label: items[index]._label,
85
- });
86
- });
87
- _.set(_clone, "data.base", base);
88
- return _clone;
89
- }
90
- );
39
+ f.data.base = merged_base;
40
+ return f;
41
+ });
91
42
  });
43
+
44
+ //: boolean | "indeterminate" | undefined
45
+ const on_change = (new_value, key) => {
46
+ field.update((f) => {
47
+ if (!f.data.base[key]) {
48
+ f.data.base[key] = {};
49
+ }
50
+ f.data.base[key].checked = new_value;
51
+ return f;
52
+ });
53
+ };
92
54
  </script>
93
55
 
94
- {#if $internal}
95
- <div class="flex flex-col w-full {!isValid ? `text-danger-500` : `text-surface-900`}">
96
- {#each items as item, index}
97
- <div class="flex space-x-3 relative mt-2">
98
- <Checkbox
99
- id="input_{state_key}_{index}"
100
- name={state_key}
101
- bind:checked={$internal.checks[index]}
102
- />
56
+ <div class="flex flex-col w-full {!isValid ? `text-danger-500` : `text-surface-900`}">
57
+ <Label class="block pb-2 {hide_label ? 'hidden' : ''}">
58
+ {label}
59
+ </Label>
60
+ {#each Object.entries(options) as [key, item] (key)}
61
+ <div class="flex space-x-3 relative mt-2">
62
+ <Checkbox
63
+ id="input_{state_key}_{key}"
64
+ name={key}
65
+ checked={$field.data?.base?.[key]?.checked || false}
66
+ onCheckedChange={(new_value) => on_change(new_value, key)}
67
+ />
103
68
 
104
- <Label for="input_{state_key}" class=" {hide_label ? 'hidden' : ''}">
105
- {item._label}
106
- </Label>
107
- </div>
108
- {/each}
109
- {#if validationMessage}
110
- <Label class={!isValid ? `text-danger-500` : `text-success-500`}>{validationMessage}</Label>
111
- {/if}
112
- </div>
113
- {/if}
69
+ <Label for="input_{state_key}_${key}" class=" {hide_label ? 'hidden' : ''}">
70
+ {item.label || startCase(key)}
71
+ </Label>
72
+ </div>
73
+ {/each}
74
+ {#if validationMessage}
75
+ <Label class={!isValid ? `text-danger-500` : `text-success-500`}>{validationMessage}</Label>
76
+ {/if}
77
+ </div>
@@ -2,6 +2,7 @@ export namespace components {
2
2
  export { Arraybuilder as arraybuilder };
3
3
  export { Checkbox as checkbox };
4
4
  export { CheckboxAutocomplete as checkbox_autocomplete };
5
+ export { Code as code };
5
6
  export { Contactselector as contactselector };
6
7
  export { Currency as currency };
7
8
  export { Dataindication as dataindication };
@@ -44,6 +45,7 @@ export namespace components {
44
45
  import Arraybuilder from "./Arraybuilder.svelte";
45
46
  import Checkbox from "./Checkbox.svelte";
46
47
  import CheckboxAutocomplete from "./CheckboxAutocomplete.svelte";
48
+ import Code from "./Code.svelte";
47
49
  import Contactselector from "./Contactselector.svelte";
48
50
  import Currency from "./Currency.svelte";
49
51
  import Dataindication from "./Dataindication.svelte";
@@ -37,11 +37,13 @@ import Text from "./Text.svelte";
37
37
  import Voicenote from "./Voicenote.svelte";
38
38
  import SmartText from "./SmartText.svelte";
39
39
  import CheckboxAutocomplete from "./CheckboxAutocomplete.svelte";
40
+ import Code from "./Code.svelte";
40
41
 
41
42
  export const components = {
42
43
  arraybuilder: Arraybuilder,
43
44
  checkbox: Checkbox,
44
45
  checkbox_autocomplete: CheckboxAutocomplete,
46
+ code: Code,
45
47
  contactselector: Contactselector,
46
48
  currency: Currency,
47
49
  dataindication: Dataindication,
@@ -0,0 +1,15 @@
1
+ {
2
+ "fieldtype": "code",
3
+ "description": "Code Editing Field",
4
+ "settings_form": {
5
+ "fields": {
6
+ "text_description": {
7
+ "fieldtype": "heading",
8
+ "params": {
9
+ "subheading_text": "Write code with syntax highlighting"
10
+ },
11
+ "__order": 0
12
+ }
13
+ }
14
+ }
15
+ }
@@ -3,6 +3,7 @@ export namespace definitions {
3
3
  export { _valid_fieldtype };
4
4
  export { arraybuilder };
5
5
  export { checkbox };
6
+ export { code };
6
7
  export { contactselector };
7
8
  export { currency };
8
9
  export { dataindication };
@@ -45,10 +46,11 @@ export const selectableFieldtypes: ({
45
46
  label: string;
46
47
  value: string;
47
48
  } | null)[];
48
- import _all from "./_all.json";
49
- import _valid_fieldtype from "./_valid_fieldtype.json";
49
+ import _all from "$lib/fields/definitions/_all.json";
50
+ import _valid_fieldtype from "$lib/fields/definitions/_valid_fieldtype.json";
50
51
  import arraybuilder from "./arraybuilder.json";
51
52
  import checkbox from "./checkbox.json";
53
+ import code from "./code.json";
52
54
  import contactselector from "./contactselector.json";
53
55
  import currency from "./currency.json";
54
56
  import dataindication from "./dataindication.json";
@@ -81,8 +83,8 @@ import select from "./select.json";
81
83
  import selectresource from "./selectresource.json";
82
84
  import signature from "./signature.json";
83
85
  import slider from "./slider.json";
84
- import smart_text from "./smart_text.json";
86
+ import smart_text from "$lib/fields/definitions/smart_text.json";
85
87
  import telephone from "./telephone.json";
86
88
  import text from "./text.json";
87
89
  import voicenote from "./voicenote.json";
88
- import _placeholder from "./_placeholder.json";
90
+ import _placeholder from "$lib/fields/definitions/_placeholder.json";
@@ -1,8 +1,9 @@
1
1
  // import _ from "lodash-es";
2
- import _all from "./_all.json";
3
- import _valid_fieldtype from "./_valid_fieldtype.json";
2
+ import _all from "$lib/fields/definitions/_all.json";
3
+ import _valid_fieldtype from "$lib/fields/definitions/_valid_fieldtype.json";
4
4
  import arraybuilder from "./arraybuilder.json";
5
5
  import checkbox from "./checkbox.json";
6
+ import code from "./code.json";
6
7
  import contactselector from "./contactselector.json";
7
8
  import currency from "./currency.json";
8
9
  import dataindication from "./dataindication.json";
@@ -36,17 +37,18 @@ import select from "./select.json";
36
37
  import selectresource from "./selectresource.json";
37
38
  import signature from "./signature.json";
38
39
  import slider from "./slider.json";
39
- import smart_text from "./smart_text.json";
40
+ import smart_text from "$lib/fields/definitions/smart_text.json";
40
41
  import telephone from "./telephone.json";
41
42
  import text from "./text.json";
42
43
  import voicenote from "./voicenote.json";
43
- import _placeholder from "./_placeholder.json";
44
+ import _placeholder from "$lib/fields/definitions/_placeholder.json";
44
45
 
45
46
  export const definitions = {
46
47
  _all,
47
48
  _valid_fieldtype,
48
49
  arraybuilder,
49
50
  checkbox,
51
+ code,
50
52
  contactselector,
51
53
  currency,
52
54
  dataindication,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stubber/form-fields",
3
- "version": "1.2.3",
3
+ "version": "1.4.0",
4
4
  "description": "An automatic form builder based on field specifications",
5
5
  "keywords": [
6
6
  "components",
@@ -36,6 +36,7 @@
36
36
  "@sveltejs/kit": "^2.0.0",
37
37
  "@sveltejs/package": "^2.2.2",
38
38
  "@sveltejs/vite-plugin-svelte": "^3.0.0",
39
+ "@types/codemirror": "^5.60.16",
39
40
  "autoprefixer": "^10.4.15",
40
41
  "chalk": "^5.3.0",
41
42
  "eslint": "^8.28.0",
@@ -55,13 +56,15 @@
55
56
  "type": "module",
56
57
  "dependencies": {
57
58
  "@beyonk/svelte-mapbox": "^11.0.0",
58
- "@codemirror/autocomplete": "^6.18.1",
59
- "@codemirror/commands": "^6.7.1",
60
- "@codemirror/state": "^6.4.1",
61
- "@codemirror/view": "^6.34.1",
59
+ "@codemirror/autocomplete": "^6.19.0",
60
+ "@codemirror/commands": "^6.8.1",
61
+ "@codemirror/lang-javascript": "^6.2.4",
62
+ "@codemirror/state": "^6.5.2",
63
+ "@codemirror/view": "^6.38.4",
62
64
  "@stubber/ui": "^1.12.3",
63
65
  "ag-grid-community": "^31.0.2",
64
66
  "ag-grid-enterprise": "^31.0.2",
67
+ "codemirror": "^6.0.2",
65
68
  "currency-symbol-map": "^5.1.0",
66
69
  "currency.js": "^2.0.4",
67
70
  "dotenv": "^16.3.1",