@stubber/form-fields 1.0.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.
- package/README.md +303 -0
- package/dist/Field.svelte +36 -0
- package/dist/Field.svelte.d.ts +31 -0
- package/dist/Form.svelte +34 -0
- package/dist/Form.svelte.d.ts +29 -0
- package/dist/NullFieldWrapper.svelte +6 -0
- package/dist/NullFieldWrapper.svelte.d.ts +25 -0
- package/dist/fields/component_parts/arraybuilder/FieldWrapper.svelte +69 -0
- package/dist/fields/component_parts/arraybuilder/FieldWrapper.svelte.d.ts +29 -0
- package/dist/fields/component_parts/fieldbuilder/FieldWrapper.svelte +8 -0
- package/dist/fields/component_parts/fieldbuilder/FieldWrapper.svelte.d.ts +25 -0
- package/dist/fields/components/AgGrid.svelte +53 -0
- package/dist/fields/components/AgGrid.svelte.d.ts +18 -0
- package/dist/fields/components/Arraybuilder.svelte +101 -0
- package/dist/fields/components/Arraybuilder.svelte.d.ts +25 -0
- package/dist/fields/components/Checkbox.svelte +100 -0
- package/dist/fields/components/Checkbox.svelte.d.ts +23 -0
- package/dist/fields/components/CheckboxAutocomplete.svelte +92 -0
- package/dist/fields/components/CheckboxAutocomplete.svelte.d.ts +23 -0
- package/dist/fields/components/Contactselector.svelte +348 -0
- package/dist/fields/components/Contactselector.svelte.d.ts +25 -0
- package/dist/fields/components/Currency.svelte +258 -0
- package/dist/fields/components/Currency.svelte.d.ts +23 -0
- package/dist/fields/components/Dataindication.svelte +35 -0
- package/dist/fields/components/Dataindication.svelte.d.ts +23 -0
- package/dist/fields/components/Date.svelte +94 -0
- package/dist/fields/components/Date.svelte.d.ts +23 -0
- package/dist/fields/components/Datetime.svelte +94 -0
- package/dist/fields/components/Datetime.svelte.d.ts +23 -0
- package/dist/fields/components/Email.svelte +124 -0
- package/dist/fields/components/Email.svelte.d.ts +23 -0
- package/dist/fields/components/Fieldbuilder.svelte +340 -0
- package/dist/fields/components/Fieldbuilder.svelte.d.ts +25 -0
- package/dist/fields/components/Fieldsbuilder.svelte +122 -0
- package/dist/fields/components/Fieldsbuilder.svelte.d.ts +25 -0
- package/dist/fields/components/File.svelte +230 -0
- package/dist/fields/components/File.svelte.d.ts +25 -0
- package/dist/fields/components/Heading.svelte +17 -0
- package/dist/fields/components/Heading.svelte.d.ts +23 -0
- package/dist/fields/components/Hidden.svelte +7 -0
- package/dist/fields/components/Hidden.svelte.d.ts +23 -0
- package/dist/fields/components/Hiddenlocation.svelte +28 -0
- package/dist/fields/components/Hiddenlocation.svelte.d.ts +23 -0
- package/dist/fields/components/Html.svelte +13 -0
- package/dist/fields/components/Html.svelte.d.ts +23 -0
- package/dist/fields/components/Jsoneditor.svelte +94 -0
- package/dist/fields/components/Jsoneditor.svelte.d.ts +23 -0
- package/dist/fields/components/Map.svelte +192 -0
- package/dist/fields/components/Map.svelte.d.ts +25 -0
- package/dist/fields/components/Multicheckbox.svelte +119 -0
- package/dist/fields/components/Multicheckbox.svelte.d.ts +23 -0
- package/dist/fields/components/Multistep.svelte +86 -0
- package/dist/fields/components/Multistep.svelte.d.ts +25 -0
- package/dist/fields/components/Note.svelte +92 -0
- package/dist/fields/components/Note.svelte.d.ts +23 -0
- package/dist/fields/components/Number.svelte +118 -0
- package/dist/fields/components/Number.svelte.d.ts +23 -0
- package/dist/fields/components/Objectbuilder.svelte +152 -0
- package/dist/fields/components/Objectbuilder.svelte.d.ts +25 -0
- package/dist/fields/components/Qrcodescanner.svelte +198 -0
- package/dist/fields/components/Qrcodescanner.svelte.d.ts +23 -0
- package/dist/fields/components/Radio.svelte +116 -0
- package/dist/fields/components/Radio.svelte.d.ts +23 -0
- package/dist/fields/components/Renderfield.svelte +58 -0
- package/dist/fields/components/Renderfield.svelte.d.ts +25 -0
- package/dist/fields/components/Screenrecorder.svelte +277 -0
- package/dist/fields/components/Screenrecorder.svelte.d.ts +25 -0
- package/dist/fields/components/Screenshot.svelte +270 -0
- package/dist/fields/components/Screenshot.svelte.d.ts +25 -0
- package/dist/fields/components/Scrollandreaddisplay.svelte +122 -0
- package/dist/fields/components/Scrollandreaddisplay.svelte.d.ts +23 -0
- package/dist/fields/components/Section.svelte +64 -0
- package/dist/fields/components/Section.svelte.d.ts +25 -0
- package/dist/fields/components/Select.svelte +229 -0
- package/dist/fields/components/Select.svelte.d.ts +23 -0
- package/dist/fields/components/Selectresource.svelte +291 -0
- package/dist/fields/components/Selectresource.svelte.d.ts +25 -0
- package/dist/fields/components/Signature.svelte +153 -0
- package/dist/fields/components/Signature.svelte.d.ts +25 -0
- package/dist/fields/components/Slider.svelte +101 -0
- package/dist/fields/components/Slider.svelte.d.ts +23 -0
- package/dist/fields/components/SmartText.svelte +330 -0
- package/dist/fields/components/SmartText.svelte.d.ts +23 -0
- package/dist/fields/components/Telephone.svelte +153 -0
- package/dist/fields/components/Telephone.svelte.d.ts +23 -0
- package/dist/fields/components/Text.svelte +106 -0
- package/dist/fields/components/Text.svelte.d.ts +23 -0
- package/dist/fields/components/Voicenote.svelte +268 -0
- package/dist/fields/components/Voicenote.svelte.d.ts +25 -0
- package/dist/fields/components/index.d.ts +81 -0
- package/dist/fields/components/index.js +82 -0
- package/dist/fields/definitions/_all.json +38 -0
- package/dist/fields/definitions/_valid_fieldtype.json +220 -0
- package/dist/fields/definitions/arraybuilder.json +39 -0
- package/dist/fields/definitions/checkbox.json +44 -0
- package/dist/fields/definitions/contactselector.json +15 -0
- package/dist/fields/definitions/currency.json +42 -0
- package/dist/fields/definitions/dataindication.json +16 -0
- package/dist/fields/definitions/date.json +16 -0
- package/dist/fields/definitions/datetime.json +15 -0
- package/dist/fields/definitions/email.json +16 -0
- package/dist/fields/definitions/fieldbuilder.json +64 -0
- package/dist/fields/definitions/fieldsbuilder.json +38 -0
- package/dist/fields/definitions/file.json +42 -0
- package/dist/fields/definitions/grid.json +47 -0
- package/dist/fields/definitions/heading.json +38 -0
- package/dist/fields/definitions/hidden.json +89 -0
- package/dist/fields/definitions/hiddenlocation.json +15 -0
- package/dist/fields/definitions/html.json +34 -0
- package/dist/fields/definitions/index.d.ts +86 -0
- package/dist/fields/definitions/index.js +96 -0
- package/dist/fields/definitions/jsoneditor.json +33 -0
- package/dist/fields/definitions/map.json +36 -0
- package/dist/fields/definitions/multicheckbox.json +47 -0
- package/dist/fields/definitions/multistep.json +35 -0
- package/dist/fields/definitions/note.json +16 -0
- package/dist/fields/definitions/number.json +42 -0
- package/dist/fields/definitions/objectbuilder.json +39 -0
- package/dist/fields/definitions/qrcodescanner.json +16 -0
- package/dist/fields/definitions/radio.json +47 -0
- package/dist/fields/definitions/renderfield.json +36 -0
- package/dist/fields/definitions/richtext.json +16 -0
- package/dist/fields/definitions/screenrecorder.json +42 -0
- package/dist/fields/definitions/screenshot.json +42 -0
- package/dist/fields/definitions/scrollandreaddisplay.json +49 -0
- package/dist/fields/definitions/section.json +50 -0
- package/dist/fields/definitions/select.json +47 -0
- package/dist/fields/definitions/selectresource.json +48 -0
- package/dist/fields/definitions/signature.json +16 -0
- package/dist/fields/definitions/slider.json +78 -0
- package/dist/fields/definitions/smart_text.json +101 -0
- package/dist/fields/definitions/telephone.json +16 -0
- package/dist/fields/definitions/text.json +35 -0
- package/dist/fields/definitions/voicenote.json +43 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/thirdparty/mapbox/GeoCoder.svelte +10 -0
- package/dist/thirdparty/mapbox/GeoCoder.svelte.d.ts +25 -0
- package/dist/thirdparty/mapbox/Map.svelte +30 -0
- package/dist/thirdparty/mapbox/Map.svelte.d.ts +20 -0
- package/dist/thirdparty/mapbox/MapMarker.svelte +13 -0
- package/dist/thirdparty/mapbox/MapMarker.svelte.d.ts +31 -0
- package/dist/utils/createField.d.ts +6 -0
- package/dist/utils/createField.js +33 -0
- package/dist/utils/createForm.d.ts +1 -0
- package/dist/utils/createForm.js +501 -0
- package/dist/utils/index.d.ts +18 -0
- package/dist/utils/index.js +126 -0
- package/dist/utils/syncing.d.ts +11 -0
- package/dist/utils/syncing.js +134 -0
- package/package.json +78 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { onMount, onDestroy } from "svelte";
|
|
3
|
+
import _ from "lodash-es";
|
|
4
|
+
import * as utils from "../../utils/index.js";
|
|
5
|
+
import { deepEqual } from "fast-equals";
|
|
6
|
+
import { writable } from "svelte/store";
|
|
7
|
+
import { syncStoreToStore } from "../../utils/syncing";
|
|
8
|
+
|
|
9
|
+
export let form;
|
|
10
|
+
export let field;
|
|
11
|
+
|
|
12
|
+
const internal = writable();
|
|
13
|
+
|
|
14
|
+
let filter_text = "";
|
|
15
|
+
let is_focused = false;
|
|
16
|
+
let input;
|
|
17
|
+
|
|
18
|
+
let dependencies = form.dependencies;
|
|
19
|
+
let clienthub = dependencies?.clienthub;
|
|
20
|
+
let createWantToModal = dependencies?.createWantToModal;
|
|
21
|
+
let stubber = dependencies?.stubber;
|
|
22
|
+
let socket = clienthub?.socket;
|
|
23
|
+
let stubref = stubber?.stubref;
|
|
24
|
+
let orguuid = stubber?.orguuid;
|
|
25
|
+
|
|
26
|
+
let clickOutside = utils.clickOutside;
|
|
27
|
+
|
|
28
|
+
$: state_key = $field.state?.state_key;
|
|
29
|
+
$: label = $field.spec?.title;
|
|
30
|
+
$: hide_label = $field.spec?.hide_label;
|
|
31
|
+
|
|
32
|
+
$: isValid = !$field.state?.validation || $field.state?.validation?.valid;
|
|
33
|
+
$: validationMessage = $field.state?.validation?.message;
|
|
34
|
+
|
|
35
|
+
onMount(() => {
|
|
36
|
+
// set field values that aren't set yet
|
|
37
|
+
let f = _.cloneDeep($field);
|
|
38
|
+
let initial_state_internal = {
|
|
39
|
+
selected_item: null,
|
|
40
|
+
raw_items: [],
|
|
41
|
+
focused_index: -1,
|
|
42
|
+
};
|
|
43
|
+
let initial_data = {
|
|
44
|
+
base: f?.data?.base,
|
|
45
|
+
base_label: f?.data?.base_label,
|
|
46
|
+
};
|
|
47
|
+
_.set(f, "data", initial_data);
|
|
48
|
+
_.set(f, "state.internal", initial_state_internal);
|
|
49
|
+
if (!deepEqual(f, $field)) $field = f;
|
|
50
|
+
|
|
51
|
+
syncStoreToStore(
|
|
52
|
+
field,
|
|
53
|
+
internal,
|
|
54
|
+
(a, b) => {
|
|
55
|
+
let _clone = _.cloneDeep(a.state?.internal) || {};
|
|
56
|
+
|
|
57
|
+
// get parts from data
|
|
58
|
+
if (a?.data?.base) {
|
|
59
|
+
_.set(_clone, "selected_item._value", a?.data?.base);
|
|
60
|
+
_.set(_clone, "selected_item._label", a?.data?.base_label);
|
|
61
|
+
}
|
|
62
|
+
filter_text = a?.data?.base_label ?? "";
|
|
63
|
+
|
|
64
|
+
// set field state if changed
|
|
65
|
+
if (!deepEqual(a?.state?.internal, _clone)) {
|
|
66
|
+
$field.state.internal = _clone;
|
|
67
|
+
}
|
|
68
|
+
return _clone;
|
|
69
|
+
},
|
|
70
|
+
(a, b) => {
|
|
71
|
+
let _clone = _.cloneDeep(a) || {};
|
|
72
|
+
// update the state
|
|
73
|
+
_.set(_clone, "state.internal", _.cloneDeep(b));
|
|
74
|
+
// update the data
|
|
75
|
+
_.set(_clone, "data.base", b?.selected_item?._value);
|
|
76
|
+
_.set(_clone, "data.base_label", b?.selected_item?._label ?? "");
|
|
77
|
+
return _clone;
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
$: items = _.isArray($internal?.raw_items)
|
|
83
|
+
? $internal?.raw_items
|
|
84
|
+
.sort((a, b) => {
|
|
85
|
+
// show all is_personal items last
|
|
86
|
+
if (a.is_personal && !b.is_personal) return 1;
|
|
87
|
+
if (!a.is_personal && b.is_personal) return -1;
|
|
88
|
+
return 0;
|
|
89
|
+
})
|
|
90
|
+
.map((item) => {
|
|
91
|
+
let _label = item[$field.spec?.params?.label || $field.spec.label] ?? item._default_label;
|
|
92
|
+
let _value = item;
|
|
93
|
+
return { _label, _value };
|
|
94
|
+
})
|
|
95
|
+
: [];
|
|
96
|
+
|
|
97
|
+
let debounceLoad = utils.debounce(loadResults, 200);
|
|
98
|
+
$: debounceLoad(filter_text);
|
|
99
|
+
async function loadResults(ft) {
|
|
100
|
+
let comparison = _.cloneDeep($internal);
|
|
101
|
+
|
|
102
|
+
if (!clienthub) return;
|
|
103
|
+
let details = {
|
|
104
|
+
resource_name: "contacts",
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
details.params = structuredClone($field.spec.params) || {};
|
|
108
|
+
// add required/dynamic params (orguuid, stubref, input, and limit)
|
|
109
|
+
details.params.orguuid = orguuid;
|
|
110
|
+
details.params.stubref = stubref;
|
|
111
|
+
details.params.input = ft;
|
|
112
|
+
if (details.params.limit === undefined) {
|
|
113
|
+
details.params.limit = 50;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// emit join page event
|
|
117
|
+
socket.emit(
|
|
118
|
+
"request",
|
|
119
|
+
{
|
|
120
|
+
type: "resource",
|
|
121
|
+
details,
|
|
122
|
+
},
|
|
123
|
+
(res) => {
|
|
124
|
+
// TODO : handle failure
|
|
125
|
+
if (res.success) {
|
|
126
|
+
comparison.raw_items = res.payload?.contacts;
|
|
127
|
+
|
|
128
|
+
if (!deepEqual(comparison, $internal)) {
|
|
129
|
+
$internal = _.cloneDeep(comparison);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let teleportedNode = null;
|
|
137
|
+
onDestroy(() => {
|
|
138
|
+
if (teleportedNode) {
|
|
139
|
+
teleportedNode.remove();
|
|
140
|
+
teleportedNode = null;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// used to show the dropdown panel in the body instead of inside the component
|
|
145
|
+
// fixes an issue where the dropdown panel would be cut off by the parent container
|
|
146
|
+
function teleport(node) {
|
|
147
|
+
// Get the original position and width
|
|
148
|
+
let rect = node.getBoundingClientRect();
|
|
149
|
+
let originalWidth = node.offsetWidth;
|
|
150
|
+
let originalHeight = node.offsetHeight;
|
|
151
|
+
|
|
152
|
+
// Teleport to the body
|
|
153
|
+
let teleportContainer = document.body;
|
|
154
|
+
teleportContainer.appendChild(node);
|
|
155
|
+
|
|
156
|
+
teleportedNode = node;
|
|
157
|
+
|
|
158
|
+
// Apply the original width and position to the teleported element
|
|
159
|
+
node.style.width = originalWidth + "px";
|
|
160
|
+
node.style.height = originalHeight + "px";
|
|
161
|
+
node.style.position = "absolute";
|
|
162
|
+
node.style.top = rect.top + window.scrollY + "px";
|
|
163
|
+
node.style.left = rect.left + "px";
|
|
164
|
+
|
|
165
|
+
// set z-index to 1000
|
|
166
|
+
node.style.zIndex = 1000;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const setSelected = (item) => {
|
|
170
|
+
if (item?.is_personal) {
|
|
171
|
+
initImport(selectedItem);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
filter_text = item._label;
|
|
175
|
+
let comparison = _.cloneDeep($internal);
|
|
176
|
+
comparison.focused_index = -1;
|
|
177
|
+
comparison.selected_item = item;
|
|
178
|
+
|
|
179
|
+
if (!deepEqual(comparison, $internal)) {
|
|
180
|
+
$internal = _.cloneDeep(comparison);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
is_focused = false;
|
|
184
|
+
input.blur();
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
function handleDeselect() {
|
|
188
|
+
let comparison = _.cloneDeep($internal);
|
|
189
|
+
comparison.selected_item = null;
|
|
190
|
+
|
|
191
|
+
if (!deepEqual(comparison, $internal)) {
|
|
192
|
+
$internal = _.cloneDeep(comparison);
|
|
193
|
+
}
|
|
194
|
+
filter_text = "";
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let createNew = false;
|
|
198
|
+
const toggleCreateNew = () => {
|
|
199
|
+
createNew = !createNew;
|
|
200
|
+
if (createNew) {
|
|
201
|
+
createWantToModal("_create_new_contact", stubref, orguuid);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const handleCloseModal = async (contact) => {
|
|
206
|
+
let res = await loadResults(contact.descname);
|
|
207
|
+
if (res.success) {
|
|
208
|
+
let firstItem = items[0];
|
|
209
|
+
if (!firstItem?.is_personal) firstItem && setSelected(firstItem);
|
|
210
|
+
else {
|
|
211
|
+
loadResults(filter_text);
|
|
212
|
+
is_focused = true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const initImport = (contact) => {
|
|
218
|
+
createWantToModal(
|
|
219
|
+
"_copy_personal_contact",
|
|
220
|
+
stubref,
|
|
221
|
+
orguuid,
|
|
222
|
+
{ _personal_contact_copy: contact },
|
|
223
|
+
() => {
|
|
224
|
+
handleCloseModal(contact);
|
|
225
|
+
return true;
|
|
226
|
+
} // need to do it like this because cb must not be async
|
|
227
|
+
);
|
|
228
|
+
};
|
|
229
|
+
</script>
|
|
230
|
+
|
|
231
|
+
{#if $internal}
|
|
232
|
+
<div
|
|
233
|
+
use:clickOutside={() => {
|
|
234
|
+
if (is_focused) {
|
|
235
|
+
is_focused = false;
|
|
236
|
+
filter_text = $field?.data?.base_label ?? "";
|
|
237
|
+
}
|
|
238
|
+
}}
|
|
239
|
+
class="relative flex flex-col w-full text-surface-900"
|
|
240
|
+
>
|
|
241
|
+
<label for="input_{state_key}" class="block text-label {hide_label ? 'hidden' : ''}">
|
|
242
|
+
{label}
|
|
243
|
+
</label>
|
|
244
|
+
<div class="relative flex mt-2 rounded-md">
|
|
245
|
+
<input
|
|
246
|
+
on:keydown={(e) => {
|
|
247
|
+
switch (e.key) {
|
|
248
|
+
case "Enter":
|
|
249
|
+
if ($internal.focused_index >= 0 && $internal.focused_index < items.length) {
|
|
250
|
+
setSelected(items[$internal.focused_index]);
|
|
251
|
+
}
|
|
252
|
+
e.preventDefault();
|
|
253
|
+
break;
|
|
254
|
+
case "ArrowDown":
|
|
255
|
+
$internal.focused_index = ($internal.focused_index + 1) % items.length;
|
|
256
|
+
e.preventDefault();
|
|
257
|
+
break;
|
|
258
|
+
case "ArrowUp":
|
|
259
|
+
$internal.focused_index = ($internal.focused_index - 1 + items.length) % items.length;
|
|
260
|
+
e.preventDefault();
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}}
|
|
264
|
+
on:focus={() => {
|
|
265
|
+
if (!is_focused) {
|
|
266
|
+
is_focused = true;
|
|
267
|
+
filter_text = "";
|
|
268
|
+
}
|
|
269
|
+
}}
|
|
270
|
+
bind:this={input}
|
|
271
|
+
type="select"
|
|
272
|
+
id="input_mask{state_key}"
|
|
273
|
+
placeholder={label}
|
|
274
|
+
class="block w-full rounded-md border-0 py-1.5 pl-3 pr-9 text-field text-surface-900 ring-1 ring-inset {!isValid
|
|
275
|
+
? 'ring-danger-500'
|
|
276
|
+
: 'ring-surface-300 focus:ring-primary-400'} focus:outline-none placeholder:text-surface-400 focus:ring-2 focus:ring-inset"
|
|
277
|
+
bind:value={filter_text}
|
|
278
|
+
autocomplete="off"
|
|
279
|
+
/>
|
|
280
|
+
{#if !$internal.selected_item}
|
|
281
|
+
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
|
282
|
+
<i class="text-surface-300 fa-regular fa-arrows-up-down fa-fw" />
|
|
283
|
+
</div>
|
|
284
|
+
{/if}
|
|
285
|
+
{#if $internal.selected_item}
|
|
286
|
+
<button
|
|
287
|
+
on:click={handleDeselect}
|
|
288
|
+
class="absolute inset-y-0 right-0 flex items-center pr-2 text-surface-300 hover:text-danger-500"
|
|
289
|
+
>
|
|
290
|
+
<i class="fa-regular fa-x fa-fw" />
|
|
291
|
+
</button>
|
|
292
|
+
{/if}
|
|
293
|
+
</div>
|
|
294
|
+
{#if is_focused}
|
|
295
|
+
<div use:teleport class="z-10 w-full absolute inset-y-[70px]">
|
|
296
|
+
{#if items?.length > 0}
|
|
297
|
+
<ul
|
|
298
|
+
class="block max-h-[200px] overflow-y-auto w-full bg-white shadow-lg rounded-md ring-1 ring-surface-300"
|
|
299
|
+
>
|
|
300
|
+
{#each items as item, index (item?._value?._key)}
|
|
301
|
+
<li
|
|
302
|
+
class="group {$internal.focused_index === index
|
|
303
|
+
? 'bg-primary-400'
|
|
304
|
+
: ''} hover:bg-primary-400 focus-within:bg-primary-400 text-field cursor-default"
|
|
305
|
+
>
|
|
306
|
+
<button
|
|
307
|
+
on:click|preventDefault={() => setSelected(item)}
|
|
308
|
+
class="selectitem text-surface-900 group-hover:text-white focus:text-white focus:outline-none px-4 py-1.5 w-full flex items-center"
|
|
309
|
+
>
|
|
310
|
+
{item._label}
|
|
311
|
+
{#if item.is_personal}
|
|
312
|
+
<div class="mx-2 bg-surface-300 rounded-full text-xs p-0.5 px-2 text-white">
|
|
313
|
+
Import from personal
|
|
314
|
+
</div>
|
|
315
|
+
{/if}
|
|
316
|
+
{#if item?._value?._key == $internal?.selected_item?._value?._key}
|
|
317
|
+
<i class="text-primary-400 ml-auto fa-regular fa-check" />
|
|
318
|
+
{/if}
|
|
319
|
+
</button>
|
|
320
|
+
</li>
|
|
321
|
+
{/each}
|
|
322
|
+
</ul>
|
|
323
|
+
{:else}
|
|
324
|
+
<div
|
|
325
|
+
class="h-[100px] flex items-center justify-center w-full bg-white shadow-lg rounded-md ring-1 ring-surface-300"
|
|
326
|
+
>
|
|
327
|
+
<span class="text-paragraph text-surface-400">Start typing to search...</span>
|
|
328
|
+
</div>
|
|
329
|
+
{/if}
|
|
330
|
+
</div>
|
|
331
|
+
{/if}
|
|
332
|
+
<div class="mt-0 ml-auto">
|
|
333
|
+
<button
|
|
334
|
+
on:click={toggleCreateNew}
|
|
335
|
+
type="button"
|
|
336
|
+
class="text-surface-500 text-sm hover:text-surface-700"
|
|
337
|
+
>
|
|
338
|
+
<i class="fa-regular fa-plus" />
|
|
339
|
+
Create new contact
|
|
340
|
+
</button>
|
|
341
|
+
</div>
|
|
342
|
+
{#if validationMessage}
|
|
343
|
+
<p class="text-label {!isValid ? `text-danger-500` : `text-success-500`}">
|
|
344
|
+
{validationMessage}
|
|
345
|
+
</p>
|
|
346
|
+
{/if}
|
|
347
|
+
</div>
|
|
348
|
+
{/if}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** @typedef {typeof __propDef.props} ContactselectorProps */
|
|
2
|
+
/** @typedef {typeof __propDef.events} ContactselectorEvents */
|
|
3
|
+
/** @typedef {typeof __propDef.slots} ContactselectorSlots */
|
|
4
|
+
export default class Contactselector extends SvelteComponentTyped<{
|
|
5
|
+
form: any;
|
|
6
|
+
field: any;
|
|
7
|
+
}, {
|
|
8
|
+
[evt: string]: CustomEvent<any>;
|
|
9
|
+
}, {}> {
|
|
10
|
+
}
|
|
11
|
+
export type ContactselectorProps = typeof __propDef.props;
|
|
12
|
+
export type ContactselectorEvents = typeof __propDef.events;
|
|
13
|
+
export type ContactselectorSlots = typeof __propDef.slots;
|
|
14
|
+
import { SvelteComponentTyped } from "svelte";
|
|
15
|
+
declare const __propDef: {
|
|
16
|
+
props: {
|
|
17
|
+
form: any;
|
|
18
|
+
field: any;
|
|
19
|
+
};
|
|
20
|
+
events: {
|
|
21
|
+
[evt: string]: CustomEvent<any>;
|
|
22
|
+
};
|
|
23
|
+
slots: {};
|
|
24
|
+
};
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { syncStoreToStore } from "../../utils/syncing";
|
|
3
|
+
import { deepEqual } from "fast-equals";
|
|
4
|
+
import _ from "lodash-es";
|
|
5
|
+
import { onMount } from "svelte";
|
|
6
|
+
import { writable } from "svelte/store";
|
|
7
|
+
import currency from "currency.js";
|
|
8
|
+
import getSymbolFromCurrency from "currency-symbol-map";
|
|
9
|
+
import * as utils from "../../utils/index.js";
|
|
10
|
+
|
|
11
|
+
export let field;
|
|
12
|
+
|
|
13
|
+
let { clickOutside, inputRegexMask } = utils;
|
|
14
|
+
|
|
15
|
+
const currencyRegex = /^([0-9]+(\.?[0-9]?[0-9]?)?)$/;
|
|
16
|
+
const currencyName = new Intl.DisplayNames("en-US", {
|
|
17
|
+
type: "currency",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const internal = writable();
|
|
21
|
+
let show_currency_list;
|
|
22
|
+
|
|
23
|
+
function toggleCurrencySelectorPopout() {
|
|
24
|
+
show_currency_list = !show_currency_list;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function handleFocusOut(e) {
|
|
28
|
+
let targetIsOption = e.relatedTarget?.classList?.contains("selectitem");
|
|
29
|
+
if (!targetIsOption) {
|
|
30
|
+
show_currency_list = false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
$: state_key = $field.state?.state_key;
|
|
35
|
+
$: label = $field.spec?.title;
|
|
36
|
+
$: hide_label = $field.spec?.hide_label;
|
|
37
|
+
$: isValid = !$field.state?.validation || $field.state?.validation?.valid;
|
|
38
|
+
$: validationMessage = $field.state?.validation?.message;
|
|
39
|
+
$: currencyFixed = $field.spec?.params?.currency;
|
|
40
|
+
$: currencyWhitelist = $field.spec?.params?.currency_whitelist ?? [];
|
|
41
|
+
$: currencyBlacklist = $field.spec?.params?.currency_blacklist ?? [];
|
|
42
|
+
$: currencyList =
|
|
43
|
+
currencyWhitelist.length > 0
|
|
44
|
+
? currencyWhitelist.filter(Boolean).map((c) => {
|
|
45
|
+
let name;
|
|
46
|
+
try {
|
|
47
|
+
name = currencyName.of(c);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.warn(err);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
value: c,
|
|
53
|
+
symbol: getSymbolFromCurrency(c),
|
|
54
|
+
name,
|
|
55
|
+
};
|
|
56
|
+
})
|
|
57
|
+
: Intl.supportedValuesOf("currency")
|
|
58
|
+
.filter((c) => {
|
|
59
|
+
return !currencyBlacklist.includes(c);
|
|
60
|
+
})
|
|
61
|
+
.map((c) => {
|
|
62
|
+
return {
|
|
63
|
+
value: c,
|
|
64
|
+
symbol: getSymbolFromCurrency(c),
|
|
65
|
+
name: currencyName.of(c),
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
function setSelectedCurrency(c) {
|
|
70
|
+
let _clone = _.cloneDeep($internal) || {};
|
|
71
|
+
_.set(_clone, "currencycode", c?.value);
|
|
72
|
+
_.set(_clone, "currencysymbol", c?.symbol);
|
|
73
|
+
$internal = _clone;
|
|
74
|
+
show_currency_list = false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
$: onRawChange($internal?.raw);
|
|
78
|
+
function onRawChange(val) {
|
|
79
|
+
if (!$internal) return;
|
|
80
|
+
let _clone = _.cloneDeep($internal) || {};
|
|
81
|
+
_clone.cents = currency(val).intValue;
|
|
82
|
+
_clone.amount = currency(val).value;
|
|
83
|
+
if (!deepEqual(_clone, $internal)) $internal = _clone;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
onMount(() => {
|
|
87
|
+
// set field values that aren't set yet
|
|
88
|
+
let f = _.cloneDeep($field);
|
|
89
|
+
let data_currencycode =
|
|
90
|
+
f?.data?.base_details?.currencycode &&
|
|
91
|
+
currencyList.find((c) => c.value === f?.data?.base_details?.currencycode)
|
|
92
|
+
? f?.data?.base_details?.currencycode
|
|
93
|
+
: null;
|
|
94
|
+
let fixed_currencycode = currencyList.find((c) => c.value === currencyFixed)
|
|
95
|
+
? currencyFixed
|
|
96
|
+
: null;
|
|
97
|
+
let currencycode = data_currencycode || fixed_currencycode || currencyList?.[0]?.value || "USD";
|
|
98
|
+
let currencysymbol = getSymbolFromCurrency(currencycode);
|
|
99
|
+
let cents = currency(f?.data?.base).intValue;
|
|
100
|
+
let amount = currency(f?.data?.base).value;
|
|
101
|
+
let initial_value = amount?.toFixed(2).toString() ?? "0.00";
|
|
102
|
+
let initial_data = {
|
|
103
|
+
...f?.data,
|
|
104
|
+
base: initial_value,
|
|
105
|
+
};
|
|
106
|
+
if (!f?.spec?.without_value_details) {
|
|
107
|
+
initial_data.base_details = {
|
|
108
|
+
currencycode,
|
|
109
|
+
currencysymbol,
|
|
110
|
+
cents,
|
|
111
|
+
amount,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let initial_state_internal = {
|
|
116
|
+
...f?.state?.internal,
|
|
117
|
+
currencycode,
|
|
118
|
+
currencysymbol,
|
|
119
|
+
cents,
|
|
120
|
+
amount,
|
|
121
|
+
raw: initial_value,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
_.set(f, "data", initial_data);
|
|
125
|
+
_.set(f, "state.internal", initial_state_internal);
|
|
126
|
+
if (!deepEqual(f, $field)) $field = f;
|
|
127
|
+
|
|
128
|
+
syncStoreToStore(
|
|
129
|
+
field,
|
|
130
|
+
internal,
|
|
131
|
+
(a, b) => {
|
|
132
|
+
let _clone = _.cloneDeep(a.state?.internal) || {};
|
|
133
|
+
|
|
134
|
+
// get parts from data
|
|
135
|
+
_clone.raw = a?.data?.base;
|
|
136
|
+
if (!a?.spec?.without_value_details) {
|
|
137
|
+
_clone.cents = a?.data?.base_details?.cents;
|
|
138
|
+
_clone.amount = a?.data?.base_details?.amount;
|
|
139
|
+
_clone.currencycode = a?.data?.base_details?.currencycode;
|
|
140
|
+
_clone.currencysymbol = a?.data?.base_details?.currencysymbol;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// set field state if changed
|
|
144
|
+
if (!deepEqual(a?.state?.internal, _clone)) {
|
|
145
|
+
$field.state.internal = _clone;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return _clone;
|
|
149
|
+
},
|
|
150
|
+
(a, b) => {
|
|
151
|
+
let _clone = _.cloneDeep(a) || {};
|
|
152
|
+
// update the state
|
|
153
|
+
_.set(_clone, "state.internal", _.cloneDeep(b));
|
|
154
|
+
// update the data
|
|
155
|
+
_.set(_clone, "data.base", b?.raw);
|
|
156
|
+
if (!a?.spec?.without_value_details) {
|
|
157
|
+
_.set(_clone, "data.base_details.amount", b?.amount);
|
|
158
|
+
_.set(_clone, "data.base_details.cents", b?.cents);
|
|
159
|
+
_.set(_clone, "data.base_details.currencycode", b?.currencycode);
|
|
160
|
+
_.set(_clone, "data.base_details.currencysymbol", b?.currencysymbol);
|
|
161
|
+
}
|
|
162
|
+
return _clone;
|
|
163
|
+
},
|
|
164
|
+
null,
|
|
165
|
+
300
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
</script>
|
|
169
|
+
|
|
170
|
+
{#if $internal}
|
|
171
|
+
<div
|
|
172
|
+
use:clickOutside={() => {
|
|
173
|
+
if (show_currency_list) {
|
|
174
|
+
show_currency_list = false;
|
|
175
|
+
}
|
|
176
|
+
}}
|
|
177
|
+
class="flex flex-col w-full text-surface-900"
|
|
178
|
+
>
|
|
179
|
+
<label for="input_{state_key}" class="block text-label {hide_label ? 'hidden' : ''}">
|
|
180
|
+
{label}
|
|
181
|
+
</label>
|
|
182
|
+
<div
|
|
183
|
+
class="flex relative mt-2 rounded-md border-0 ring-1 {!isValid
|
|
184
|
+
? 'ring-danger-500'
|
|
185
|
+
: 'ring-surface-300 focus:ring-primary-400'} focus-within:ring-2"
|
|
186
|
+
>
|
|
187
|
+
<div class="pointer-events-none flex items-center pl-3">
|
|
188
|
+
<span class="text-label text-surface-600">
|
|
189
|
+
{getSymbolFromCurrency($internal?.currencycode)}
|
|
190
|
+
</span>
|
|
191
|
+
</div>
|
|
192
|
+
<input
|
|
193
|
+
on:keydown={(e) => {
|
|
194
|
+
if (e.key === "Enter") {
|
|
195
|
+
e.preventDefault();
|
|
196
|
+
}
|
|
197
|
+
}}
|
|
198
|
+
use:inputRegexMask={currencyRegex}
|
|
199
|
+
id="input_{state_key}"
|
|
200
|
+
placeholder={label}
|
|
201
|
+
class="block w-full rounded-md py-2 px-3 focus:outline-none text-surface-900 placeholder:text-surface-400"
|
|
202
|
+
name={state_key}
|
|
203
|
+
inputmode="decimal"
|
|
204
|
+
bind:value={$internal.raw}
|
|
205
|
+
on:focus={(e) => e.target.select()}
|
|
206
|
+
/>
|
|
207
|
+
<!-- Here be selector stuff -->
|
|
208
|
+
{#if currencyList && !currencyFixed}
|
|
209
|
+
<button
|
|
210
|
+
type="button"
|
|
211
|
+
on:click={toggleCurrencySelectorPopout}
|
|
212
|
+
class="w-20 pl-3 space-x-2 flex items-center bg-white hover:bg-surface-50 focus:outline-none focus:bg-surface-100 rounded-r-md"
|
|
213
|
+
>
|
|
214
|
+
<span class="text-field text-surface-700">{$internal?.currencycode}</span>
|
|
215
|
+
<i
|
|
216
|
+
class="text-surface-700 fa fa-solid fa-xs fa-caret-{show_currency_list ? 'up' : 'down'}"
|
|
217
|
+
/>
|
|
218
|
+
</button>
|
|
219
|
+
{/if}
|
|
220
|
+
{#if show_currency_list && currencyList}
|
|
221
|
+
<div class="z-10 absolute inset-y-[42px] right-0">
|
|
222
|
+
<ul
|
|
223
|
+
class="items block max-h-[250px] overflow-y-auto w-[250px] md:w-[450px] bg-white shadow-lg ring-1 ring-surface-300"
|
|
224
|
+
>
|
|
225
|
+
{#each currencyList as item}
|
|
226
|
+
<!-- {#if currencyWhitelist.contains(item.value) && !currencyBlacklist.contains(item.value)} -->
|
|
227
|
+
<li
|
|
228
|
+
class="{item.value == $internal?.currencycode
|
|
229
|
+
? 'selecteditem'
|
|
230
|
+
: ''} group hover:bg-surface-100 focus-within:bg-surface-100 text-field cursor-default"
|
|
231
|
+
>
|
|
232
|
+
<button
|
|
233
|
+
on:click|preventDefault={() => setSelectedCurrency(item)}
|
|
234
|
+
on:focusout={(e) => handleFocusOut(e)}
|
|
235
|
+
class="px-4 py-1 w-full flex items-center space-x-3 selectitem focus:outline-none"
|
|
236
|
+
>
|
|
237
|
+
<span class="text-surface-900 text-field">{item.value}</span>
|
|
238
|
+
<span class="text-surface-700 text-field">{item.symbol}</span>
|
|
239
|
+
<span class="text-surface-500 text-field">{item.name}</span>
|
|
240
|
+
{#if item.value == $internal?.currencycode}
|
|
241
|
+
<i class="text-primary-400 ml-auto fa-regular fa-check" />
|
|
242
|
+
{/if}
|
|
243
|
+
</button>
|
|
244
|
+
</li>
|
|
245
|
+
<!-- {/if} -->
|
|
246
|
+
{/each}
|
|
247
|
+
</ul>
|
|
248
|
+
</div>
|
|
249
|
+
{/if}
|
|
250
|
+
<!-- Here end selector stuff -->
|
|
251
|
+
</div>
|
|
252
|
+
{#if validationMessage}
|
|
253
|
+
<p class="text-label {!isValid ? `text-danger-500` : `text-success-500`}">
|
|
254
|
+
{validationMessage}
|
|
255
|
+
</p>
|
|
256
|
+
{/if}
|
|
257
|
+
</div>
|
|
258
|
+
{/if}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @typedef {typeof __propDef.props} CurrencyProps */
|
|
2
|
+
/** @typedef {typeof __propDef.events} CurrencyEvents */
|
|
3
|
+
/** @typedef {typeof __propDef.slots} CurrencySlots */
|
|
4
|
+
export default class Currency extends SvelteComponentTyped<{
|
|
5
|
+
field: any;
|
|
6
|
+
}, {
|
|
7
|
+
[evt: string]: CustomEvent<any>;
|
|
8
|
+
}, {}> {
|
|
9
|
+
}
|
|
10
|
+
export type CurrencyProps = typeof __propDef.props;
|
|
11
|
+
export type CurrencyEvents = typeof __propDef.events;
|
|
12
|
+
export type CurrencySlots = typeof __propDef.slots;
|
|
13
|
+
import { SvelteComponentTyped } from "svelte";
|
|
14
|
+
declare const __propDef: {
|
|
15
|
+
props: {
|
|
16
|
+
field: any;
|
|
17
|
+
};
|
|
18
|
+
events: {
|
|
19
|
+
[evt: string]: CustomEvent<any>;
|
|
20
|
+
};
|
|
21
|
+
slots: {};
|
|
22
|
+
};
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import _ from "lodash-es";
|
|
3
|
+
|
|
4
|
+
export let field;
|
|
5
|
+
|
|
6
|
+
$: state_key = $field.state?.state_key;
|
|
7
|
+
$: label = $field.spec?.title;
|
|
8
|
+
$: hide_label = $field.spec?.hide_label;
|
|
9
|
+
$: isValid = !$field.state?.validation || $field.state?.validation?.valid;
|
|
10
|
+
$: validationMessage = $field.state?.validation?.message;
|
|
11
|
+
$: value_is_string = _.isString($field.data?.base);
|
|
12
|
+
$: value = !value_is_string ? JSON.stringify($field.data?.base, null, 2) : $field.data?.base;
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<div class="flex flex-col w-full text-surface-900">
|
|
16
|
+
<label for="input_{state_key}" class="block text-label {hide_label ? 'hidden' : ''}">
|
|
17
|
+
{label}
|
|
18
|
+
</label>
|
|
19
|
+
<div class="relative mt-2 rounded-md">
|
|
20
|
+
<div
|
|
21
|
+
class="block bg-surface-50 w-full text-field rounded-md border-0 py-2 pl-3 text-surface-900 ring-1 ring-inset ring-surface-100 focus:ring-primary-400 focus:outline-none placeholder:text-surface-400 focus:ring-2 focus:ring-inset"
|
|
22
|
+
>
|
|
23
|
+
{#if value_is_string}
|
|
24
|
+
{value}
|
|
25
|
+
{:else}
|
|
26
|
+
<pre>{value}</pre>
|
|
27
|
+
{/if}
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
{#if validationMessage}
|
|
31
|
+
<p class="text-label {!isValid ? `text-danger-500` : `text-success-500`}">
|
|
32
|
+
{validationMessage}
|
|
33
|
+
</p>
|
|
34
|
+
{/if}
|
|
35
|
+
</div>
|