@soleil-se/config-svelte 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.
Files changed (54) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +5 -0
  3. package/actions/httpPrefix.js +10 -0
  4. package/actions/index.js +3 -0
  5. package/actions/resizer.js +50 -0
  6. package/actions/show.js +12 -0
  7. package/components/Checkbox/Component.svelte +78 -0
  8. package/components/Checkbox/README.md +52 -0
  9. package/components/Checkbox/index.js +1 -0
  10. package/components/CheckboxGroup/Component.svelte +114 -0
  11. package/components/CheckboxGroup/README.md +89 -0
  12. package/components/CheckboxGroup/index.js +1 -0
  13. package/components/DropdownSelector/Component.svelte +82 -0
  14. package/components/DropdownSelector/README.md +75 -0
  15. package/components/DropdownSelector/index.js +1 -0
  16. package/components/InputField/Component.svelte +76 -0
  17. package/components/InputField/README.md +89 -0
  18. package/components/InputField/index.js +1 -0
  19. package/components/LinkSelector/Component.svelte +133 -0
  20. package/components/LinkSelector/README.md +93 -0
  21. package/components/LinkSelector/index.js +1 -0
  22. package/components/ListSelector/Component.svelte +72 -0
  23. package/components/ListSelector/README.md +96 -0
  24. package/components/ListSelector/index.js +1 -0
  25. package/components/NodeSelector/Component.svelte +81 -0
  26. package/components/NodeSelector/README.md +90 -0
  27. package/components/NodeSelector/index.js +1 -0
  28. package/components/NumberSpinner/Component.svelte +65 -0
  29. package/components/NumberSpinner/README.md +57 -0
  30. package/components/NumberSpinner/index.js +1 -0
  31. package/components/Panel/Component.svelte +33 -0
  32. package/components/Panel/README.md +25 -0
  33. package/components/Panel/index.js +1 -0
  34. package/components/RadioGroup/Component.svelte +105 -0
  35. package/components/RadioGroup/README.md +74 -0
  36. package/components/RadioGroup/index.js +1 -0
  37. package/components/SelectField/Component.svelte +37 -0
  38. package/components/SelectField/README.md +67 -0
  39. package/components/SelectField/index.js +1 -0
  40. package/components/TextList/Component.svelte +267 -0
  41. package/components/TextList/README.md +57 -0
  42. package/components/TextList/index.js +1 -0
  43. package/components/TextList/internal/Sortable.svelte +43 -0
  44. package/createConfigApp/index.js +17 -0
  45. package/index.js +16 -0
  46. package/package.json +29 -0
  47. package/server/createAppData/index.js +8 -0
  48. package/server/index.js +3 -0
  49. package/utils/addPrefix.js +8 -0
  50. package/utils/generateId.js +12 -0
  51. package/utils/index.js +5 -0
  52. package/utils/onSave.js +8 -0
  53. package/utils/pluckPrefix.js +21 -0
  54. package/utils/setupComponent.js +4 -0
@@ -0,0 +1,267 @@
1
+ <script>
2
+ import { createEventDispatcher, tick } from 'svelte';
3
+ import { generateId } from '../../utils';
4
+ import { resizer } from '../../actions';
5
+ import Sortable from './internal/Sortable.svelte';
6
+
7
+ const values = window.CONFIG_VALUES || {};
8
+ const dispatch = createEventDispatcher();
9
+
10
+ export let id = generateId();
11
+ export let label;
12
+ export let required = false;
13
+ export let name = undefined;
14
+ export let disabled = false;
15
+ export let value = [];
16
+ value = name ? values[name] ?? value : value;
17
+
18
+ const createItem = (props) => ({
19
+ id: generateId(),
20
+ value: '',
21
+ edit: false,
22
+ focus: false,
23
+ hover: false,
24
+ ...props,
25
+ });
26
+
27
+ let addButtonElement;
28
+ const itemElements = [];
29
+ let dragging = false;
30
+ let items = value.map((val) => createItem({ value: val }));
31
+
32
+ $: editIndex = items.findIndex(({ edit }) => edit);
33
+ $: isEditing = editIndex > -1;
34
+
35
+ const onChange = () => {
36
+ value = items.filter(({ edit }) => !edit).map((item) => item.value);
37
+ dispatch('input', value);
38
+ dispatch('change', value);
39
+ };
40
+
41
+ const addItem = async () => {
42
+ if (!isEditing) {
43
+ items = items.concat(createItem({ edit: true }));
44
+ await tick();
45
+ }
46
+ itemElements[editIndex].querySelector('input').focus();
47
+ };
48
+
49
+ const editItem = async (index) => {
50
+ items = items.map((item) => ({ ...item, edit: false }));
51
+ items[index].edit = true;
52
+ items[index].oldValue = items[index].value;
53
+
54
+ await tick();
55
+ itemElements[index].querySelector('input').focus();
56
+ };
57
+
58
+ const removeItem = (index) => {
59
+ items.splice(index, 1);
60
+ items = items;
61
+ onChange();
62
+ };
63
+
64
+ const saveEdit = (index) => {
65
+ items[index].edit = false;
66
+ delete items[index].oldValue;
67
+
68
+ if (!items[index].value) {
69
+ removeItem(index);
70
+ }
71
+
72
+ onChange();
73
+ };
74
+
75
+ const updateItem = (index, props) => {
76
+ items[index] = {
77
+ ...items[index],
78
+ ...props,
79
+ };
80
+ };
81
+
82
+ const abortEdit = (index) => {
83
+ items[index].edit = false;
84
+ items[index].value = items[index].oldValue;
85
+ delete items[index].oldValue;
86
+
87
+ if (!items[index].value) {
88
+ removeItem(index);
89
+ }
90
+ };
91
+
92
+ const handleKeypress = (index, { keyCode }) => {
93
+ // Enter
94
+ if (keyCode === 13) {
95
+ saveEdit(index);
96
+ addButtonElement.focus();
97
+ }
98
+ // Esc
99
+ if (keyCode === 27) {
100
+ abortEdit(index);
101
+ addButtonElement.focus();
102
+ }
103
+ };
104
+
105
+ const onSortEnd = ({ detail }) => {
106
+ items = detail;
107
+ dragging = false;
108
+ onChange();
109
+ };
110
+ </script>
111
+
112
+ <div class="form-group">
113
+ <label for={id} class="control-label {required ? 'control-label--required' : ''}">{label}</label>
114
+ <div class="list-component">
115
+ <div class="list-component__list-wrapper ui-resizable">
116
+ <Sortable
117
+ class="list-component__list {dragging ? 'dragging' : ''}"
118
+ {items}
119
+ on:start={() => {
120
+ dragging = true;
121
+ }}
122
+ on:end={onSortEnd}
123
+ >
124
+ {#each items as item, index (item.id)}
125
+ <li
126
+ bind:this={itemElements[index]}
127
+ class="list-component__item"
128
+ class:focus={item.focus}
129
+ class:hover={item.hover}
130
+ on:mouseenter={() => updateItem(index, { hover: !dragging })}
131
+ on:mouseleave={() => updateItem(index, { hover: dragging })}
132
+ >
133
+ {#if item.edit}
134
+ <div class="form-group">
135
+ <div class="input-group">
136
+ <input
137
+ {id}
138
+ bind:value={item.value}
139
+ type="text"
140
+ class="form-control"
141
+ on:keypress={(e) => handleKeypress(index, e)}
142
+ />
143
+
144
+ <span class="input-group-btn">
145
+ <button class="btn btn-default" type="button" on:click={() => saveEdit(index)}>
146
+ <i class="glyphicons ok-2" />
147
+ </button>
148
+ <button class="btn btn-default" type="button" on:click={() => abortEdit(index)}>
149
+ <i class="glyphicons remove-2" />
150
+ </button>
151
+ </span>
152
+ </div>
153
+ </div>
154
+ {:else}
155
+ <div on:dblclick={() => editItem(index)}>
156
+ <span class="item-name">{item.value}</span>
157
+ <div class="list-component__item-actions">
158
+ <button
159
+ on:click={() => editItem(index)}
160
+ on:focusin={() => updateItem(index, { focus: true })}
161
+ on:focusout={() => updateItem(index, { focus: false })}
162
+ >
163
+ <i class="glyphicons edit" />
164
+ </button>
165
+ <button
166
+ on:click={() => removeItem(index)}
167
+ on:focusin={() => updateItem(index, { focus: true })}
168
+ on:focusout={() => updateItem(index, { focus: false })}
169
+ >
170
+ <i class="glyphicons remove-2" />
171
+ </button>
172
+ <i class="glyphicons move" />
173
+ </div>
174
+ </div>
175
+ {/if}
176
+ </li>
177
+ {/each}
178
+ </Sortable>
179
+ <div use:resizer class="ui-resizable-handle ui-resizable-s" />
180
+ </div>
181
+ <button
182
+ bind:this={addButtonElement}
183
+ class="list-component__add-item btn btn-link"
184
+ on:click={addItem}
185
+ >
186
+ <i class="glyphicons plus" />
187
+ Lägg till...
188
+ </button>
189
+ </div>
190
+ {#if disabled}
191
+ <div class="disabled-overlay" />
192
+ {/if}
193
+ <select {id} {value} multiple {name} class="sr-only" aria-hidden="true">
194
+ {#each value as option}
195
+ <option>{option}</option>
196
+ {/each}
197
+ </select>
198
+ </div>
199
+
200
+ <style lang="scss" global>
201
+ :global(html) {
202
+ overflow: hidden;
203
+ }
204
+
205
+ .list-component {
206
+ & &__list-wrapper {
207
+ height: 120px;
208
+ min-height: 90px;
209
+ }
210
+
211
+ & &__item {
212
+ &:focus {
213
+ .glyphicons {
214
+ display: inline-block;
215
+ }
216
+ }
217
+
218
+ &:hover {
219
+ background-color: white;
220
+
221
+ .glyphicons {
222
+ display: none;
223
+ }
224
+ }
225
+
226
+ &.chosen,
227
+ &.focus,
228
+ &.hover {
229
+ background-color: #f5f5f5;
230
+
231
+ .glyphicons {
232
+ display: inline-block;
233
+ }
234
+ }
235
+
236
+ .dragging &:not(.chosen) {
237
+ &:hover {
238
+ background-color: white;
239
+ }
240
+
241
+ .glyphicons {
242
+ display: none;
243
+ }
244
+ }
245
+
246
+ &-actions {
247
+ button {
248
+ padding: 0;
249
+ background: none;
250
+ border: none;
251
+ -webkit-appearance: none;
252
+ appearance: none;
253
+ }
254
+ }
255
+ }
256
+ }
257
+
258
+ .disabled-overlay {
259
+ position: absolute;
260
+ top: 25px;
261
+ bottom: 0;
262
+ z-index: 90;
263
+ width: 100%;
264
+ background-color: rgba(255, 255, 255, 0.3);
265
+ cursor: not-allowed;
266
+ }
267
+ </style>
@@ -0,0 +1,57 @@
1
+ # TextList
2
+
3
+ ## Props
4
+ ```javascript
5
+ export let id = generateId();
6
+ export let label;
7
+ export let required = false;
8
+ export let name = undefined;
9
+ export let disabled = false;
10
+ export let value = [];
11
+ ```
12
+
13
+ ## Default value
14
+ Use the value attribute to set a default value:
15
+ ```svelte
16
+ <TextList
17
+ name="textList"
18
+ label="Text List"
19
+ value={['Text 1']}
20
+ />
21
+ ```
22
+
23
+ ## Example
24
+
25
+ ### Standard
26
+ ```svelte
27
+ <script>
28
+ import { Panel, TextList } from '@soleil-se/svelte-config';
29
+ </script>
30
+
31
+ <Panel heading="Inställningar">
32
+ <TextList
33
+ name="textList"
34
+ label="Text List"
35
+ />
36
+ </Panel>
37
+ ```
38
+ ### Advanced
39
+
40
+ ```svelte
41
+ <script>
42
+ import { Panel, TextList } from '@soleil-se/svelte-config';
43
+ import { onSave } from '@soleil-se/svelte-config/utils';
44
+
45
+ const values = {
46
+ textList: '',
47
+ ...window.CONFIG_VALUES
48
+ };
49
+
50
+ onSave(() => values);
51
+
52
+ </script>
53
+
54
+ <Panel heading="Inställningar">
55
+ <TextList bind:value={values.textList} label="Text List" />
56
+ </Panel>
57
+ ```
@@ -0,0 +1 @@
1
+ export { default } from './Component.svelte';
@@ -0,0 +1,43 @@
1
+ <script>
2
+ import { onMount, createEventDispatcher, tick } from 'svelte';
3
+ import { Sortable } from 'sortablejs';
4
+
5
+ const dispatch = createEventDispatcher();
6
+
7
+ export let items = [];
8
+
9
+ let element;
10
+
11
+ function onChoose({ oldIndex }) {
12
+ dispatch('choose', oldIndex);
13
+ }
14
+
15
+ function onStart() {
16
+ dispatch('start', items);
17
+ }
18
+
19
+ async function onEnd({ oldIndex, newIndex }) {
20
+ const copy = [...items];
21
+ const [item] = copy.splice(oldIndex, 1);
22
+ copy.splice(newIndex, 0, item);
23
+ await tick();
24
+ dispatch('end', copy);
25
+ }
26
+
27
+ onMount(() => {
28
+ Sortable.create(element, {
29
+ ghostClass: 'ghost',
30
+ chosenClass: 'chosen',
31
+ dragClass: 'drag',
32
+ filter: '.form-group',
33
+ preventOnFilter: false,
34
+ onChoose,
35
+ onStart,
36
+ onEnd,
37
+ });
38
+ });
39
+ </script>
40
+
41
+ <ul bind:this={element} class={$$props.class}>
42
+ <slot />
43
+ </ul>
@@ -0,0 +1,17 @@
1
+ import 'focus-visible';
2
+
3
+ export default function createConfigApp(App, { hydrate = true } = {}) {
4
+ window.setValues = (values) => {
5
+ window.CONFIG_VALUES = values;
6
+ const app = new App({
7
+ target: document.querySelector('#app_root, body'),
8
+ props: {
9
+ ...window.CONFIG_APP_DATA,
10
+ values,
11
+ },
12
+ hydrate,
13
+ });
14
+
15
+ return app;
16
+ };
17
+ }
package/index.js ADDED
@@ -0,0 +1,16 @@
1
+ export { default as Panel } from './components/Panel';
2
+ export { default as Checkbox } from './components/Checkbox';
3
+ export { default as CheckboxGroup } from './components/CheckboxGroup';
4
+ export { default as DropdownSelector } from './components/DropdownSelector';
5
+ export { default as InputField } from './components/InputField';
6
+ export { default as LinkSelector } from './components/LinkSelector';
7
+ export { default as ListSelector } from './components/ListSelector';
8
+ export { default as NodeSelector } from './components/NodeSelector';
9
+ export { default as NumberSpinner } from './components/NumberSpinner';
10
+ export { default as RadioGroup } from './components/RadioGroup';
11
+ export { default as SelectField } from './components/SelectField';
12
+ export { default as TextList } from './components/TextList';
13
+
14
+ export { default as createConfigApp } from './createConfigApp';
15
+
16
+ export { generateId, onSave, setupComponent } from './utils';
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@soleil-se/config-svelte",
3
+ "version": "1.0.0",
4
+ "main": "./index.js",
5
+ "module": "./index.js",
6
+ "svelte": "./index.js",
7
+ "author": "Soleil AB",
8
+ "license": "UNLICENSED",
9
+ "sideEffects": false,
10
+ "peerDependencies": {
11
+ "svelte": "^3.26.0"
12
+ },
13
+ "devDependencies": {
14
+ "svelte": "^3.26.0"
15
+ },
16
+ "dependencies": {
17
+ "focus-visible": "^5.1.0",
18
+ "sortablejs": "^1.12.0"
19
+ },
20
+ "homepage": "https://docs.soleil.se/packages/config-svelte",
21
+ "description": "A collection of Svelte components and utilities for WebApp, RESTApp and Widget configurations.",
22
+ "keywords": [
23
+ "Sitevision",
24
+ "WebApp",
25
+ "RESTApp",
26
+ "Widget",
27
+ "Svelte"
28
+ ]
29
+ }
@@ -0,0 +1,8 @@
1
+ export default function createAppData(data) {
2
+ return `
3
+ <script>
4
+ window.CONFIG_APP_DATA = ${JSON.stringify(data)};
5
+ </script>
6
+ <div id="app_root"></div>
7
+ `;
8
+ }
@@ -0,0 +1,3 @@
1
+ /* Might add more exports in the future */
2
+ /* eslint-disable import/prefer-default-export */
3
+ export { default as createAppData } from './createAppData';
@@ -0,0 +1,8 @@
1
+ const capitalizeFirstLetter = (string) => string.charAt(0).toUpperCase() + string.slice(1);
2
+
3
+ export default function addPrefix(prefix, value) {
4
+ return Object.entries(value).reduce((obj, [key, val]) => ({
5
+ ...obj,
6
+ [prefix + capitalizeFirstLetter(key)]: val,
7
+ }), {});
8
+ }
@@ -0,0 +1,12 @@
1
+ const generated = [];
2
+
3
+ const generate = () => Math.random().toString(36).substr(2, 9);
4
+
5
+ export default function generateId(prefix = 'input') {
6
+ const id = `${prefix}_${generate()}`;
7
+ if (generated.includes(id)) {
8
+ return generateId(prefix);
9
+ }
10
+ generated.push(id);
11
+ return id;
12
+ }
package/utils/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { default as generateId } from './generateId';
2
+ export { default as onSave } from './onSave';
3
+ export { default as setupComponent } from './setupComponent';
4
+ export { default as addPrefix } from './addPrefix';
5
+ export { default as pluckPrefix } from './pluckPrefix';
@@ -0,0 +1,8 @@
1
+ export default function onSave(callback) {
2
+ window.getValues = () => {
3
+ const values = JSON.parse(JSON.stringify(callback()));
4
+ // Timestamp to get changes to be saved in SiteVision
5
+ values.lastEdit = new Date().getTime();
6
+ return values;
7
+ };
8
+ }
@@ -0,0 +1,21 @@
1
+ const lowerFirstLetter = (string) => string.charAt(0).toLowerCase() + string.slice(1);
2
+ /**
3
+ * Plucks object properties with prefixed keys, removes the prefix and returns an object with
4
+ * the properties that was prefixed.
5
+ * @export
6
+ * @param {String} prefix A prefix.
7
+ * @param {Object} [values=window.CONFIG_VALUES] Object with values.
8
+ * @returns {Object} An object with plucked properties where prefix is removed from keys.
9
+ */
10
+ export default function pluckPrefix(prefix, values = window.CONFIG_VALUES) {
11
+ return Object.entries(values).reduce((obj, [key, value]) => {
12
+ if (key.startsWith(prefix)) {
13
+ const newKey = lowerFirstLetter(key.replace(prefix, ''));
14
+ return {
15
+ ...obj,
16
+ [newKey]: value,
17
+ };
18
+ }
19
+ return obj;
20
+ }, {});
21
+ }
@@ -0,0 +1,4 @@
1
+ export default function setupComponent(elemId) {
2
+ $('body').trigger('setup-component', `#${elemId}`);
3
+ return $(`#${elemId}`);
4
+ }