@soleil-se/config-svelte 1.3.0 → 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.
package/CHANGELOG.md CHANGED
@@ -5,7 +5,26 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [1.3.0] - 2021-04.28
8
+ ## [1.4.0] - 2021-05-19
9
+
10
+ ## Changed
11
+
12
+ - TextList component rewrite.
13
+ - Remove CheckboxGroup value workaround as it's not needed.
14
+
15
+ ## Fixed
16
+
17
+ - Type shorthand for ListSelector.
18
+ - Was possible to circumvent alt-text requirement in ImageSelector
19
+
20
+ ## [1.3.1] - 2021-04-29
21
+
22
+ ## Fixed
23
+
24
+ - Default value for LinkSelector type is not set.
25
+ - ImageSelector does not clear alt-text from image archive.
26
+
27
+ ## [1.3.0] - 2021-04-28
9
28
 
10
29
  ## Added
11
30
 
@@ -20,11 +20,11 @@ const resizer = (element) => {
20
20
  const resizeElement = (e) => {
21
21
  const offset = e.y - mousePos;
22
22
  mousePos = e.y;
23
- const targetHeight = parseInt(getComputedStyle(targetElement).height, 10);
24
- const targetOffset = targetElement.getBoundingClientRect().top;
25
23
 
24
+ const targetOffset = targetElement.getBoundingClientRect().top;
26
25
  if (e.y > (targetOffset + targetMinHeight)) {
27
- // eslint-disable-next-line no-param-reassign
26
+ const targetHeight = parseInt(getComputedStyle(targetElement).height, 10);
27
+ // eslint-disable-next-line no-param-reassign
28
28
  targetElement.style.height = `${targetHeight + offset}px`;
29
29
  }
30
30
  };
@@ -37,6 +37,7 @@
37
37
  bind:group={value}
38
38
  class="sr-only"
39
39
  value={option.value || option}
40
+ {name}
40
41
  type="checkbox"
41
42
  disabled={isDisabled(option)}
42
43
  on:change={onChange}
@@ -51,14 +52,6 @@
51
52
  </label>
52
53
  {/each}
53
54
  </fieldset>
54
- <!-- Workaround to properly save values as array when using name -->
55
- {#if name}
56
- <select {value} multiple {name} class="sr-only" aria-hidden="true">
57
- {#each value as option}
58
- <option>{option}</option>
59
- {/each}
60
- </select>
61
- {/if}
62
55
 
63
56
  <style lang="scss">
64
57
  fieldset {
@@ -111,4 +104,5 @@
111
104
  input {
112
105
  margin: -1px;
113
106
  }
107
+
114
108
  </style>
@@ -24,13 +24,18 @@
24
24
 
25
25
  const dispatch = createEventDispatcher();
26
26
 
27
+ const setCustomValidity = () => {
28
+ validationEl?.setCustomValidity(imageArchiveAlt ? '' : 'Bilden saknar beskrivning (alt-text). Klicka på bilden ovan och skriv texten i fältet Bildbeskrivning.');
29
+ };
30
+
27
31
  const onChange = async () => {
28
32
  if (node) {
29
33
  const response = await fetch(`/rest-api/1/1/${node}/properties`).then((res) => res.json());
30
34
  imageArchiveAlt = response.alt || '';
31
35
 
32
- validationEl?.setCustomValidity(imageArchiveAlt ? '' : 'Bilden saknar beskrivning (alt-text). Klicka på bilden ovan och skriv texten i fältet Bildbeskrivning.');
36
+ setCustomValidity();
33
37
  } else {
38
+ imageArchiveAlt = '';
34
39
  validationEl?.setCustomValidity('');
35
40
  }
36
41
  triggerInput(validationEl);
@@ -45,6 +50,7 @@
45
50
  } else {
46
51
  customAlt = alt;
47
52
  alt = undefined;
53
+ setCustomValidity();
48
54
  }
49
55
  };
50
56
 
@@ -75,4 +81,5 @@
75
81
  div {
76
82
  margin-top: -10px;
77
83
  }
84
+
78
85
  </style>
@@ -4,16 +4,14 @@ const getDefaultValue = (types) => ({
4
4
  value: '',
5
5
  });
6
6
 
7
- export default function getSavedValue({ value, name, types }) {
7
+ export default function getSavedValue({ value = {}, name, types }) {
8
8
  const values = window.CONFIG_VALUES || {};
9
9
 
10
+ const defaultValue = getDefaultValue(types);
11
+
10
12
  return {
11
- ...getDefaultValue(types),
12
- ...value,
13
- ...(name ? {
14
- type: values[`${name}Type`],
15
- newWindow: values[`${name}NewWindow`],
16
- value: values[`${name}Value`],
17
- } : {}),
13
+ type: value.type || values[`${name}Type`] || defaultValue.type,
14
+ newWindow: value.newWindow || values[`${name}NewWindow`] || defaultValue.newWindow,
15
+ value: value.value || values[`${name}Value`] || defaultValue.value,
18
16
  };
19
17
  }
@@ -23,7 +23,7 @@
23
23
  dispatch('change', value);
24
24
  };
25
25
 
26
- $: component = type.endsWith('-list') ? type : `${type}-page`;
26
+ $: component = type.endsWith('-list') ? type : `${type}-list`;
27
27
 
28
28
  onMount(() => {
29
29
  const element = setupComponent(id)[0];
@@ -71,4 +71,5 @@
71
71
  background-color: rgba(255, 255, 255, 0.3);
72
72
  cursor: not-allowed;
73
73
  }
74
+
74
75
  </style>
@@ -1,8 +1,9 @@
1
1
  <script>
2
- import { createEventDispatcher, tick } from 'svelte';
2
+ import { createEventDispatcher } from 'svelte';
3
3
  import { generateId } from '../../utils';
4
4
  import { resizer } from '../../actions';
5
5
  import Sortable from './internal/Sortable.svelte';
6
+ import Item from './internal/Item.svelte';
6
7
  import DataHolder from './internal/DataHolder.svelte';
7
8
 
8
9
  const values = window.CONFIG_VALUES || {};
@@ -20,18 +21,14 @@
20
21
  id: generateId(),
21
22
  value: '',
22
23
  edit: false,
23
- focus: false,
24
- hover: false,
25
24
  ...props,
26
25
  });
27
26
 
28
27
  let addButtonElement;
29
- const itemElements = [];
30
- let dragging = false;
28
+
31
29
  let items = value.map((val) => createItem({ value: val }));
32
30
 
33
- $: editIndex = items.findIndex(({ edit }) => edit);
34
- $: isEditing = editIndex > -1;
31
+ $: isEditing = items.findIndex(({ edit }) => edit) > -1;
35
32
 
36
33
  const onChange = () => {
37
34
  value = items.filter(({ edit }) => !edit).map((item) => item.value);
@@ -42,72 +39,29 @@
42
39
  const addItem = async () => {
43
40
  if (!isEditing) {
44
41
  items = items.concat(createItem({ edit: true }));
45
- await tick();
46
42
  }
47
- itemElements[editIndex].querySelector('input').focus();
48
- };
49
-
50
- const editItem = async (index) => {
51
- items = items.map((item) => ({ ...item, edit: false }));
52
- items[index].edit = true;
53
- items[index].oldValue = items[index].value;
54
-
55
- await tick();
56
- itemElements[index].querySelector('input').focus();
57
43
  };
58
44
 
59
- const removeItem = (index) => {
60
- items.splice(index, 1);
61
- items = items;
62
- onChange();
45
+ const onEdit = async ({ detail }) => {
46
+ items = items.map((item) => ({ ...item, edit: item.id === detail }));
63
47
  };
64
48
 
65
- const saveEdit = (index) => {
66
- items[index].edit = false;
67
- delete items[index].oldValue;
68
-
69
- if (!items[index].value) {
70
- removeItem(index);
71
- }
72
-
49
+ const onRemove = ({ detail }) => {
50
+ items = items.filter((item) => item.id !== detail);
73
51
  onChange();
74
52
  };
75
53
 
76
- const updateItem = (index, props) => {
54
+ const onUpdate = ({ detail }) => {
55
+ const index = items.findIndex((item) => item.id === detail.id);
77
56
  if (items[index]) {
78
- items[index] = {
79
- ...items[index],
80
- ...props,
81
- };
82
- }
83
- };
84
-
85
- const abortEdit = (index) => {
86
- items[index].edit = false;
87
- items[index].value = items[index].oldValue;
88
- delete items[index].oldValue;
89
-
90
- if (!items[index].value) {
91
- removeItem(index);
92
- }
93
- };
94
-
95
- const handleKeypress = (index, { keyCode }) => {
96
- // Enter
97
- if (keyCode === 13) {
98
- saveEdit(index);
99
- addButtonElement.focus();
100
- }
101
- // Esc
102
- if (keyCode === 27) {
103
- abortEdit(index);
104
- addButtonElement.focus();
57
+ items[index].value = detail.value;
58
+ items[index].edit = false;
105
59
  }
60
+ onChange();
106
61
  };
107
62
 
108
- const onSortEnd = ({ detail }) => {
63
+ const onFinalize = ({ detail }) => {
109
64
  items = detail;
110
- dragging = false;
111
65
  onChange();
112
66
  };
113
67
  </script>
@@ -116,68 +70,14 @@
116
70
  <label for={id} class="control-label {required ? 'control-label--required' : ''}">{label}</label>
117
71
  <div class="list-component">
118
72
  <div class="list-component__list-wrapper ui-resizable">
119
- <Sortable
120
- class="list-component__list {dragging ? 'dragging' : ''}"
121
- {items}
122
- on:start={() => {
123
- dragging = true;
124
- }}
125
- on:end={onSortEnd}
126
- >
127
- {#each items as item, index (item.id)}
128
- <li
129
- bind:this={itemElements[index]}
130
- class="list-component__item"
131
- class:focus={item.focus}
132
- class:hover={item.hover}
133
- on:mouseenter={() => updateItem(index, { hover: !dragging })}
134
- on:mouseleave={() => updateItem(index, { hover: dragging })}
135
- >
136
- {#if item.edit}
137
- <div class="form-group">
138
- <div class="input-group">
139
- <input
140
- {id}
141
- bind:value={item.value}
142
- type="text"
143
- class="form-control"
144
- on:keypress={(e) => handleKeypress(index, e)}
145
- />
146
-
147
- <span class="input-group-btn">
148
- <button class="btn btn-default" type="button" on:click={() => saveEdit(index)}>
149
- <i class="glyphicons ok-2" />
150
- </button>
151
- <button class="btn btn-default" type="button" on:click={() => abortEdit(index)}>
152
- <i class="glyphicons remove-2" />
153
- </button>
154
- </span>
155
- </div>
156
- </div>
157
- {:else}
158
- <div on:dblclick={() => editItem(index)}>
159
- <span class="item-name">{item.value}</span>
160
- <div class="list-component__item-actions">
161
- <button
162
- on:click={() => editItem(index)}
163
- on:focusin={() => updateItem(index, { focus: true })}
164
- on:focusout={() => updateItem(index, { focus: false })}
165
- >
166
- <i class="glyphicons edit" />
167
- </button>
168
- <button
169
- on:click={() => removeItem(index)}
170
- on:focusin={() => updateItem(index, { focus: true })}
171
- on:focusout={() => updateItem(index, { focus: false })}
172
- >
173
- <i class="glyphicons remove-2" />
174
- </button>
175
- <i class="glyphicons move" />
176
- </div>
177
- </div>
178
- {/if}
179
- </li>
180
- {/each}
73
+ <Sortable {items} on:finalize={onFinalize} let:item>
74
+ <Item
75
+ {...item}
76
+ on:edit={onEdit}
77
+ on:remove={onRemove}
78
+ on:update={onUpdate}
79
+ on:enter={() => addButtonElement.focus()}
80
+ />
181
81
  </Sortable>
182
82
  <div use:resizer class="ui-resizable-handle ui-resizable-s" />
183
83
  </div>
@@ -186,7 +86,7 @@
186
86
  class="list-component__add-item btn btn-link"
187
87
  on:click={addItem}
188
88
  >
189
- <i class="glyphicons plus" />
89
+ <i class="glyphicons plus" aria-hidden="true" />
190
90
  Lägg till...
191
91
  </button>
192
92
  </div>
@@ -196,58 +96,9 @@
196
96
  <DataHolder {id} {name} {value} {required} />
197
97
  </div>
198
98
 
199
- <style lang="scss" global>
200
- .list-component {
201
- & &__list-wrapper {
202
- height: 120px;
203
- min-height: 90px;
204
- }
205
-
206
- & &__item {
207
- &:focus {
208
- .glyphicons {
209
- display: inline-block;
210
- }
211
- }
212
-
213
- &:hover {
214
- background-color: white;
215
-
216
- .glyphicons {
217
- display: none;
218
- }
219
- }
220
-
221
- &.chosen,
222
- &.focus,
223
- &.hover {
224
- background-color: #f5f5f5;
225
-
226
- .glyphicons {
227
- display: inline-block;
228
- }
229
- }
230
-
231
- .dragging &:not(.chosen) {
232
- &:hover {
233
- background-color: white;
234
- }
235
-
236
- .glyphicons {
237
- display: none;
238
- }
239
- }
240
-
241
- &-actions {
242
- button {
243
- padding: 0;
244
- background: none;
245
- border: none;
246
- -webkit-appearance: none;
247
- appearance: none;
248
- }
249
- }
250
- }
99
+ <style lang="scss">
100
+ .list-component__list-wrapper {
101
+ min-height: 120px;
251
102
  }
252
103
 
253
104
  .disabled-overlay {
@@ -259,4 +110,5 @@
259
110
  background-color: rgba(255, 255, 255, 0.3);
260
111
  cursor: not-allowed;
261
112
  }
113
+
262
114
  </style>
@@ -0,0 +1,119 @@
1
+ <script>
2
+ import { tick, createEventDispatcher, onMount } from 'svelte';
3
+
4
+ export let id;
5
+ export let value;
6
+ export let edit = false;
7
+
8
+ const dispatch = createEventDispatcher();
9
+
10
+ let oldValue;
11
+ let inputElement;
12
+
13
+ const editItem = async () => {
14
+ oldValue = value;
15
+ dispatch('edit', id);
16
+ await tick();
17
+ inputElement.focus();
18
+ };
19
+
20
+ const removeItem = () => {
21
+ dispatch('remove', id);
22
+ };
23
+
24
+ const updateItem = () => {
25
+ if (!value) {
26
+ removeItem();
27
+ } else {
28
+ dispatch('update', { id, value });
29
+ }
30
+ };
31
+
32
+ const abortEdit = () => {
33
+ value = oldValue;
34
+ dispatch('edit');
35
+ if (!value) {
36
+ removeItem();
37
+ }
38
+ };
39
+
40
+ const handleKeypress = ({ keyCode }) => {
41
+ // Enter
42
+ if (edit && keyCode === 13) {
43
+ updateItem();
44
+ dispatch('enter');
45
+ }
46
+ // Esc
47
+ if (edit && keyCode === 27) {
48
+ abortEdit();
49
+ }
50
+ };
51
+
52
+ onMount(() => {
53
+ if (edit) inputElement.focus();
54
+ });
55
+ </script>
56
+
57
+ {#if edit}
58
+ <div class="form-group">
59
+ <div class="input-group">
60
+ <input
61
+ {id}
62
+ bind:value
63
+ type="text"
64
+ class="form-control"
65
+ on:keypress={handleKeypress}
66
+ bind:this={inputElement}
67
+ />
68
+
69
+ <span class="input-group-btn">
70
+ <button class="btn btn-default" type="button" on:click={updateItem}>
71
+ <i class="glyphicons ok-2" />
72
+ </button>
73
+ <button class="btn btn-default" type="button" on:click={abortEdit}>
74
+ <i class="glyphicons remove-2" />
75
+ </button>
76
+ </span>
77
+ </div>
78
+ </div>
79
+ {:else}
80
+ <div on:dblclick={editItem}>
81
+ <span class="item-name">{value}</span>
82
+ <div class="list-component__item-actions">
83
+ <button on:click={editItem}>
84
+ <i class="glyphicons edit" aria-hidden="true" />
85
+ </button>
86
+ <button on:click={removeItem}>
87
+ <i class="glyphicons remove-2" aria-hidden="true" />
88
+ </button>
89
+ <i class="glyphicons move" aria-hidden="true" />
90
+ </div>
91
+ </div>
92
+ {/if}
93
+
94
+ <style lang="scss">
95
+ button {
96
+ &:focus {
97
+ outline: none;
98
+ }
99
+
100
+ &:focus-visible {
101
+ outline: 2px solid #66afe9;
102
+ }
103
+ }
104
+
105
+ .list-component__item-actions {
106
+ button {
107
+ padding: 0;
108
+ background: none;
109
+ border: none;
110
+ -webkit-appearance: none;
111
+ appearance: none;
112
+ }
113
+
114
+ :global(li:focus-within) & i {
115
+ display: inline-block;
116
+ }
117
+ }
118
+
119
+ </style>
@@ -1,43 +1,55 @@
1
1
  <script>
2
- import { onMount, createEventDispatcher, tick } from 'svelte';
3
- import { Sortable } from 'sortablejs';
2
+ import { createEventDispatcher } from 'svelte';
3
+ import { dndzone } from 'svelte-dnd-action';
4
+ import { flip } from 'svelte/animate';
5
+
6
+ export let items;
4
7
 
5
8
  const dispatch = createEventDispatcher();
6
9
 
7
- export let items = [];
10
+ $: dragDisabled = items.some(({ edit }) => edit);
8
11
 
9
- let element;
12
+ const onConsider = (e) => {
13
+ items = e.detail.items;
14
+ };
10
15
 
11
- function onChoose({ oldIndex }) {
12
- dispatch('choose', oldIndex);
13
- }
16
+ const onFinalize = (e) => {
17
+ dispatch('finalize', e.detail.items);
18
+ };
14
19
 
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
- });
20
+ const transformDraggedElement = (el) => {
21
+ // eslint-disable-next-line no-param-reassign
22
+ el.style.backgroundColor = '#f5f5f5';
23
+ };
39
24
  </script>
40
25
 
41
- <ul bind:this={element} class={$$props.class}>
42
- <slot />
26
+ <ul
27
+ use:dndzone={{
28
+ items,
29
+ flipDurationMs: 100,
30
+ dropFromOthersDisabled: true,
31
+ transformDraggedElement,
32
+ dropTargetStyle: {},
33
+ autoAriaDisabled: true,
34
+ dragDisabled,
35
+ }}
36
+ on:consider={onConsider}
37
+ on:finalize={onFinalize}
38
+ class="list-component__list"
39
+ >
40
+ {#each items as item (item.id)}
41
+ <li class="list-component__item" animate:flip={{ duration: 100 }}>
42
+ <slot {item} />
43
+ </li>
44
+ {/each}
43
45
  </ul>
46
+
47
+ <style>
48
+ ul:focus,
49
+ ul :global(li.focus-visible) {
50
+ position: relative;
51
+ z-index: 1;
52
+ outline: 2px solid #66afe9;
53
+ }
54
+
55
+ </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soleil-se/config-svelte",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "main": "./index.js",
5
5
  "module": "./index.js",
6
6
  "svelte": "./index.js",
@@ -8,14 +8,14 @@
8
8
  "license": "UNLICENSED",
9
9
  "sideEffects": false,
10
10
  "peerDependencies": {
11
- "svelte": "^3.26.0"
11
+ "svelte": "^3.38.2"
12
12
  },
13
13
  "devDependencies": {
14
- "svelte": "^3.26.0"
14
+ "svelte": "^3.38.2"
15
15
  },
16
16
  "dependencies": {
17
- "focus-visible": "^5.1.0",
18
- "sortablejs": "^1.12.0"
17
+ "focus-visible": "^5.2.0",
18
+ "svelte-dnd-action": "^0.9.8"
19
19
  },
20
20
  "homepage": "https://docs.soleil.se/packages/config-svelte",
21
21
  "description": "A collection of Svelte components and utilities for WebApp, RESTApp and Widget configurations.",