@soleil-se/config-svelte 1.16.2 → 1.18.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,6 +5,18 @@ 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.18.0] - 2023-01-20
9
+
10
+ - Option templating in [CustomSelector](./components/CustomSelector).
11
+ - Add global Select2 styling.
12
+ - Modal now closes open Select2 dropdowns.
13
+
14
+ ## [1.17.0] - 2022-12-01
15
+
16
+ - New component: [RepositoryNodeSelector](./components/RepositoryNodeSelector).
17
+ - New utility API: [getAppContext](./#getappcontext).
18
+ - New server API: [createAppContext](./#context)
19
+
8
20
  ## [1.16.2] - 2022-11-24
9
21
 
10
22
  - [ListSelector](./components/ListSelector) now works properly in [CustomList](./components/CustomList).
@@ -2,12 +2,13 @@
2
2
  import { createEventDispatcher, onMount } from 'svelte';
3
3
  import { generateId, addGeneratedIds } from '../../utils';
4
4
  import { resizer } from '../../actions';
5
+ import i18n from './i18n';
5
6
 
6
7
  import Sortable from './internal/Sortable.svelte';
7
8
  import SortableItem from './internal/SortableItem.svelte';
8
9
  import DataHolder from './internal/DataHolder.svelte';
9
10
  import EditModal from './internal/EditModal.svelte';
10
- import CreateItem from './internal/CreateItem.svelte';
11
+ import AddModal from './internal/AddModal.svelte';
11
12
 
12
13
  const values = window.CONFIG_VALUES || {};
13
14
  const dispatch = createEventDispatcher();
@@ -29,6 +30,15 @@
29
30
  let items = [];
30
31
  let editId;
31
32
  let dragDisabled = disabled;
33
+ let addModalOpen = false;
34
+
35
+ const onAddOpen = () => {
36
+ addModalOpen = true;
37
+ };
38
+
39
+ const onAddClose = () => {
40
+ addModalOpen = false;
41
+ };
32
42
 
33
43
  const updateItems = () => {
34
44
  items = value.map((id) => ({
@@ -43,7 +53,7 @@
43
53
  dispatch('change', value);
44
54
  };
45
55
 
46
- const onCreate = ({ detail }) => {
56
+ const onAddSave = ({ detail }) => {
47
57
  items = items.concat(detail);
48
58
  modals = modals.concat(detail.id);
49
59
  onChange();
@@ -97,6 +107,9 @@
97
107
  <slot {id} {index} />
98
108
  </EditModal>
99
109
  {/each}
110
+ <AddModal {name} open={addModalOpen} on:save={onAddSave} on:close={onAddClose} let:id>
111
+ <slot {id} index={items.length} />
112
+ </AddModal>
100
113
 
101
114
  <div class="form-group" class:hidden={!show}>
102
115
  <label for={domId} class="control-label {required ? 'control-label--required' : ''}">
@@ -116,10 +129,11 @@
116
129
  </Sortable>
117
130
  <div use:resizer class="ui-resizable-handle ui-resizable-s" />
118
131
  </div>
132
+ <button class="list-component__add-item btn btn-link" on:click={onAddOpen} {disabled}>
133
+ <i class="glyphicons plus" aria-hidden="true" />
134
+ {i18n('add')}
135
+ </button>
119
136
 
120
- <CreateItem {name} {disabled} on:create={onCreate} let:id>
121
- <slot {id} index={items.length} />
122
- </CreateItem>
123
137
  {#if disabled}
124
138
  <div class="disabled-overlay" />
125
139
  {/if}
@@ -148,4 +162,16 @@
148
162
  cursor: not-allowed;
149
163
  background-color: rgb(255 255 255 / 30%);
150
164
  }
165
+
166
+ button {
167
+ &:focus,
168
+ &:active {
169
+ color: #585858;
170
+ outline: none;
171
+ }
172
+
173
+ &:focus-visible {
174
+ outline: 2px solid #66afe9;
175
+ }
176
+ }
151
177
  </style>
@@ -3,17 +3,17 @@ import createI18n from '../../utils/createI18n';
3
3
  export default createI18n({
4
4
  sv: {
5
5
  add: 'Lägg till...',
6
- validation: 'Lägg till minst en post i listan',
6
+ validation: 'Lägg till minst en post i listan.',
7
7
  edit: 'Redigera',
8
8
  },
9
9
  no: {
10
10
  add: 'Legg till...',
11
- validation: 'Legg til minst ett element i listen',
11
+ validation: 'Legg til minst ett element i listen.',
12
12
  edit: 'Redigere',
13
13
  },
14
14
  en: {
15
15
  add: 'Add...',
16
- validation: 'Add at least one item to the list',
16
+ validation: 'Add at least one item to the list.',
17
17
  edit: 'Edit',
18
18
  },
19
19
  });
@@ -6,26 +6,22 @@
6
6
  import Modal from '../../Modal';
7
7
 
8
8
  export let name;
9
- export let disabled;
9
+ export let open = false;
10
10
 
11
11
  let id;
12
12
  let element;
13
- let open = false;
14
13
 
15
14
  const dispatch = createEventDispatcher();
16
15
 
17
- const addItem = async () => {
18
- open = true;
19
- id = generateId(name || 'item');
20
- };
16
+ $: if (open) id = generateId(name || 'item');
21
17
 
22
18
  const onClose = () => {
23
- open = false;
19
+ dispatch('close');
24
20
  setTimeout(() => (id = undefined), 300);
25
21
  };
26
22
 
27
23
  const onSave = async () => {
28
- dispatch('create', {
24
+ dispatch('save', {
29
25
  id,
30
26
  name: element.querySelector('input')?.value || id,
31
27
  });
@@ -38,22 +34,3 @@
38
34
  <slot {id} />
39
35
  {/if}
40
36
  </Modal>
41
-
42
- <button class="list-component__add-item btn btn-link" on:click={addItem} {disabled}>
43
- <i class="glyphicons plus" aria-hidden="true" />
44
- {i18n('add')}
45
- </button>
46
-
47
- <style lang="scss">
48
- button {
49
- &:focus,
50
- &:active {
51
- color: #585858;
52
- outline: none;
53
- }
54
-
55
- &:focus-visible {
56
- outline: 2px solid #66afe9;
57
- }
58
- }
59
- </style>
@@ -1,6 +1,7 @@
1
1
  <script>
2
- import { createEventDispatcher, onMount } from 'svelte';
3
- import { generateId, setupComponent } from '../../utils';
2
+ import { createEventDispatcher, onMount, tick } from 'svelte';
3
+ import { generateId } from '../../utils';
4
+ import i18n from './i18n';
4
5
 
5
6
  const values = window.CONFIG_VALUES || {};
6
7
  const dispatch = createEventDispatcher();
@@ -8,9 +9,9 @@
8
9
  export let id = generateId();
9
10
  export let label;
10
11
  export let name = undefined;
12
+ export let placeholder = i18n('placeholder');
11
13
  export let options = [];
12
14
  export let searchable = false;
13
- export let placeholder;
14
15
  export let required = false;
15
16
  export let disabled = false;
16
17
  export let multiple = false;
@@ -18,21 +19,46 @@
18
19
 
19
20
  export let value = multiple ? [] : '';
20
21
 
21
- value = name ? values[name] ?? value : value;
22
- // value = value === Object(value) ? value.value : value;
22
+ value = name ? values[name]?.value ?? values[name] ?? value : value;
23
+ value = Array.isArray(value) ? value.map((v) => v.value ?? v) : value;
23
24
 
24
25
  let className = '';
25
26
  export { className as class };
26
27
 
28
+ let element;
29
+
30
+ async function triggerChange() {
31
+ await tick();
32
+ jQuery(element).val(value).trigger('change');
33
+ }
34
+
35
+ $: if (element && options) triggerChange();
36
+
37
+ function template(option) {
38
+ const content = document.querySelector(`[data-option="${id}_${option.id}"]`)?.innerHTML;
39
+ return content ? jQuery(content) : option.text;
40
+ }
41
+
27
42
  onMount(() => {
28
- const [el] = setupComponent(id)
43
+ // Instansiate select2 ourselves to be able to use more options.
44
+ jQuery(element)
45
+ .select2({
46
+ placeholder,
47
+ width: '100%',
48
+ allowClear: true,
49
+ minimumResultsForSearch: searchable ? 0 : Infinity,
50
+ templateResult: template,
51
+ formatResult: template,
52
+ templateSelection: template,
53
+ formatSelection: template,
54
+ })
29
55
  .on('change', (e) => {
30
- value = e.val || value;
31
- dispatch('input', value);
32
- dispatch('change', value);
56
+ if (e.val !== undefined && value !== e.val) {
57
+ value = e.val;
58
+ dispatch('input', value);
59
+ dispatch('change', value);
60
+ }
33
61
  });
34
-
35
- jQuery(el).val(value).trigger('change');
36
62
  });
37
63
  </script>
38
64
 
@@ -44,18 +70,23 @@
44
70
  -->
45
71
 
46
72
  <div class="form-group {className}" class:hidden={!show}>
73
+ {#if $$slots.option}
74
+ {#each options as option}
75
+ <span hidden data-option="{id}_{option.value ?? option}">
76
+ <slot name="option" {option} />
77
+ </span>
78
+ {/each}
79
+ {/if}
47
80
  <label for={id} class="control-label {required ? 'control-label--required' : ''}">{label}</label>
48
81
  <select
82
+ bind:this={element}
49
83
  {id}
50
84
  {required}
51
85
  {disabled}
52
86
  {multiple}
53
87
  name={!multiple ? name : undefined}
54
- data-component="custom-selector"
55
- data-removable
56
- data-searchable={searchable ? '' : undefined}
57
- data-placeholder={placeholder ? '' : undefined}
58
88
  >
89
+ <option />
59
90
  {#each options as option}
60
91
  <option value={option.value ?? option}>
61
92
  {option.label ?? option}
@@ -71,19 +102,3 @@
71
102
  </select>
72
103
  {/if}
73
104
  </div>
74
-
75
- <style>
76
- div :global(.select2-container-multi .select2-search-choice-close) {
77
- top: 3px;
78
- }
79
-
80
- div :global(.select2-container-multi ul.select2-choices) {
81
- border: 1px solid #ccc;
82
- box-shadow: inset 0 1px 1px rgb(0 0 0 / 8%);
83
- }
84
-
85
- div :global(.select2-container-multi .select2-choices .select2-search-choice) {
86
- padding: 4px 5px 4px 18px;
87
- margin-top: 4px;
88
- }
89
- </style>
@@ -11,9 +11,9 @@ Wrapper component for Sitevision [Custom selector](https://developer.sitevision.
11
11
  export let id = generateId();
12
12
  export let label; // Required
13
13
  export let name = undefined;
14
+ export let placeholder;
14
15
  export let options = [];
15
16
  export let searchable = false;
16
- export let placeholder;
17
17
  export let required = false;
18
18
  export let disabled = false;
19
19
  export let multiple = false;
@@ -73,3 +73,24 @@ Default slot after selector.
73
73
  <p class="help-block">Some helpful text.</p>
74
74
  </CustomSelector>
75
75
  ```
76
+
77
+ Option slot for custom templating.
78
+ The option is available as slot prop.
79
+
80
+ ```svelte
81
+ <script>
82
+ import { Panel, CustomSelector } from '@soleil-se/svelte-config';
83
+
84
+ const options = [
85
+ { label: 'Värde 1', value: 'value1', type: 'Typ 1' },
86
+ { label: 'Värde 2', value: 'value2', type: 'Typ 2' },
87
+ { label: 'Värde 3', value: 'value3', type: 'Typ 3' },
88
+ ];
89
+ </script>
90
+
91
+ <Panel>
92
+ <CustomSelector name="custom" label="Custom selector" {options} >
93
+ <span slot="option" let:option>{option.label} ({option.type})</span>
94
+ </CustomSelector>
95
+ </Panel>
96
+ ```
@@ -0,0 +1,13 @@
1
+ import createI18n from '../../utils/createI18n';
2
+
3
+ export default createI18n({
4
+ sv: {
5
+ placeholder: 'Välj',
6
+ },
7
+ no: {
8
+ placeholder: 'Velj',
9
+ },
10
+ en: {
11
+ placeholder: 'Select',
12
+ },
13
+ });
@@ -42,9 +42,9 @@
42
42
  });
43
43
 
44
44
  /*
45
- * The sv:loaded event is only triggered on initial load, if the component rerenders check if any
46
- * children are present and set value of select element.
47
- */
45
+ * The sv:loaded event is only triggered on initial load, if the component rerenders check if any
46
+ * children are present and set value of select element.
47
+ */
48
48
  if (el?.childElementCount > 0) {
49
49
  setValue(el);
50
50
  }
@@ -71,34 +71,3 @@
71
71
  />
72
72
  <slot />
73
73
  </div>
74
-
75
- <style global>
76
- .select2-container .select2-choice {
77
- height: 34px;
78
- padding-left: 12px;
79
- line-height: 32px;
80
- border: 1px solid #ccc;
81
- box-shadow: inset 0 1px 1px rgb(0 0 0 / 7.5%);
82
- }
83
-
84
- .select2-container .select2-choice .select2-arrow b {
85
- background-position-y: 3px;
86
- }
87
-
88
- .select2-container .select2-choice abbr {
89
- top: 9px;
90
- }
91
-
92
- .select2-container.select2-container-disabled .select2-choice {
93
- cursor: not-allowed;
94
- background-color: white;
95
- background-image: none;
96
- border: 1px solid #ccc;
97
- opacity: 0.7;
98
- }
99
-
100
- .select2-container.select2-container-disabed .select2-choice .select2-arrow {
101
- background-color: white;
102
- border-left: 1px solid #aaa;
103
- }
104
- </style>
@@ -52,6 +52,7 @@ Possible values for `type`:
52
52
  - `image`
53
53
  - `user`
54
54
  - `userfield`
55
+ - `template`
55
56
 
56
57
  For `tags-selector` use [TagSelector](../TagSelector).
57
58
 
@@ -56,6 +56,7 @@
56
56
  a11yDialog.show();
57
57
  } else {
58
58
  a11yDialog.hide();
59
+ jQuery(element).find('.select2-offscreen')?.select2('close');
59
60
  }
60
61
  }
61
62
  };
@@ -23,14 +23,14 @@
23
23
  </div>
24
24
  </div>
25
25
 
26
- <style lang="scss">
27
- :global(html) {
26
+ <style lang="scss" global>
27
+ html {
28
28
  position: fixed;
29
29
  overflow: hidden;
30
30
  background-color: transparent;
31
31
  }
32
32
 
33
- :global(body) {
33
+ body {
34
34
  overflow: auto;
35
35
  background: none;
36
36
  }
@@ -47,4 +47,47 @@
47
47
  content: '*';
48
48
  }
49
49
  }
50
+
51
+ .select2-container-multi .select2-search-choice-close {
52
+ top: 3px;
53
+ }
54
+
55
+ .select2-container-multi ul.select2-choices {
56
+ border: 1px solid #ccc;
57
+ box-shadow: inset 0 1px 1px rgb(0 0 0 / 8%);
58
+ }
59
+
60
+ .select2-container-multi .select2-choices .select2-search-choice {
61
+ padding: 4px 5px 4px 18px;
62
+ margin-top: 4px;
63
+ }
64
+
65
+ .select2-container .select2-choice {
66
+ height: 34px;
67
+ padding-left: 12px;
68
+ line-height: 32px;
69
+ border: 1px solid #ccc;
70
+ box-shadow: inset 0 1px 1px rgb(0 0 0 / 7.5%);
71
+ }
72
+
73
+ .select2-container .select2-choice .select2-arrow b {
74
+ background-position-y: 3px;
75
+ }
76
+
77
+ .select2-container .select2-choice abbr {
78
+ top: 9px;
79
+ }
80
+
81
+ .select2-container.select2-container-disabled .select2-choice {
82
+ cursor: not-allowed;
83
+ background-color: white;
84
+ background-image: none;
85
+ border: 1px solid #ccc;
86
+ opacity: 0.7;
87
+ }
88
+
89
+ .select2-container.select2-container-disabed .select2-choice .select2-arrow {
90
+ background-color: white;
91
+ border-left: 1px solid #aaa;
92
+ }
50
93
  </style>
@@ -0,0 +1,79 @@
1
+ <!-- prettier-ignore -->
2
+ <script>
3
+ import { onMount } from 'svelte';
4
+ import { getAppContext } from '../../utils';
5
+ import CustomSelector from '../CustomSelector';
6
+ import i18n from './i18n';
7
+
8
+ export let id;
9
+ export let label;
10
+ export let name;
11
+ export let searchable;
12
+ export let required;
13
+ export let disabled;
14
+ export let multiple;
15
+ export let show;
16
+ export let value;
17
+
18
+ export let type;
19
+
20
+ let options = [];
21
+ let error = false;
22
+
23
+ function toTitleCase(str) {
24
+ return str
25
+ .replace(/^[-_]*(.)/, (_, c) => c.toUpperCase())
26
+ .replace(/[-_]+(.)/g, (_, c) => ` ${c.toUpperCase()}`);
27
+ }
28
+
29
+ function getEndpoint() {
30
+ const { siteName } = getAppContext();
31
+ let repository = toTitleCase(type);
32
+ repository = !repository.endsWith('Repository') ? `${repository} Repository` : repository;
33
+ const endpoint = repository.includes('Alias') ? 'aliases' : 'nodes';
34
+
35
+ return `/rest-api/1/0/${siteName}/${repository}/${endpoint}`;
36
+ }
37
+
38
+ onMount(async () => {
39
+ const endpoint = getEndpoint();
40
+ const result = await fetch(endpoint)
41
+ .then((res) => {
42
+ if (res.ok) return res;
43
+ error = true;
44
+ throw new Error(
45
+ `Error (${res.status}) - Unable to fetch nodes, is site name and repository correct?`,
46
+ );
47
+ })
48
+ .then((res) => res.json())
49
+ .catch((e) => console.error(e.message));
50
+
51
+ options = (result || []).map((item) => ({ label: item.name, value: item.id }));
52
+ });
53
+ </script>
54
+
55
+ <CustomSelector
56
+ {id}
57
+ {label}
58
+ {name}
59
+ {searchable}
60
+ {required}
61
+ {disabled}
62
+ {multiple}
63
+ {show}
64
+ {options}
65
+ {value}
66
+ on:input
67
+ on:change
68
+ >
69
+ <slot />
70
+ {#if error}
71
+ <p class="alert alert-danger">{@html i18n('error')}</p>
72
+ {/if}
73
+ </CustomSelector>
74
+
75
+ <style>
76
+ p.alert {
77
+ margin-top: 10px;
78
+ }
79
+ </style>
@@ -0,0 +1,135 @@
1
+ # RepositoryNodeSelector
2
+
3
+ Component for selecting one or multiple nodes from a repository, is using [CustomSelector](../CustomSelector).
4
+
5
+ ![RepositoryNodeSelector1](../../images/RepositoryNodeSelector1.png)
6
+ ![RepositoryNodeSelector2](../../images/RepositoryNodeSelector2.png)
7
+
8
+ ## Props
9
+
10
+ ```javascript
11
+ export let id = generateId();
12
+ export let label; // Required
13
+ export let name = undefined;
14
+ export let options = [];
15
+ export let searchable = false;
16
+ export let required = false;
17
+ export let disabled = false;
18
+ export let multiple = false;
19
+ export let show = true;
20
+ export let value = '';
21
+ export let type;
22
+ ```
23
+
24
+ ### Types
25
+
26
+ All repositories that are children to the site are valid types, some are more useful than others.
27
+
28
+ ::: details Available types
29
+
30
+ Following types are available, not all are useful though.
31
+
32
+ #### Useful
33
+
34
+ * `alias` - Alias Repository.
35
+ * `named-reference` - Named Reference Repository.
36
+ * `rss-feed` - RSS Feed Repository.
37
+ * `rating-type` - Rating Type Repository.
38
+ * `responsive-breakpoint` - Responsive Breakpoint Repository.
39
+ * `role` - Role Repository.
40
+ * `site-cookie` - Site Cookie Repository.
41
+ * `tag-group` - Tag Group Repository.
42
+ * `tag` - Tag Repository.
43
+ * `user-field` - User Field Repository.
44
+
45
+ #### Do not use
46
+
47
+ * `color` - Color Repository, use `<DropdownSelector type="color" />` instead.
48
+ * `file` - File Repository, use `<NodeSelector type="file" />` instead.
49
+ * `font` - Font Repository, use `<DropdownSelector type="font" />` instead.
50
+ * `image` - Image Repository, use `<NodeSelector type="image" />` instead.
51
+ * `index` - Index Repository, use `<DropdownSelector type="search-index" />` instead.
52
+ * `metadata` - Metadata Definition Template Repository, use `<DropdownSelector type="metadata" />` instead.
53
+ * `oAuth2` - OAuth2 Configuration Repository, use `<DropdownSelector type="oauth2-configuration" />` instead.
54
+ * `page` - Page Repository, use `<NodeSelector type="page" />` instead.
55
+ * `principal` - Principal Repository, use `<NodeSelector type="user" />` instead.
56
+ * `template` - Template Repository, use `<DropdownSelector type="template" />` instead.
57
+
58
+ #### Other
59
+
60
+ * `addon` - Addon Repository.
61
+ * `data-store` - Data Store Repository.
62
+ * `decoration` - Decoration Repository.
63
+ * `default-image` - Default Image Repository.
64
+ * `externaltopicintegration` - ExternalTopicIntegration Repository.
65
+ * `icon` - Icon Repository.
66
+ * `list-style` - List Style Repository.
67
+ * `module-element-draft` - Module Element Draft Repository.
68
+ * `module-element` - Module Element Repository.
69
+ * `restApp` - RestApp Repository.
70
+ * `topic` - Topic Repository.
71
+ * `webApp` - WebApp Repository.
72
+
73
+ :::
74
+
75
+ ## App context
76
+
77
+ To be able to use this component app context must be created in `config/index.js`.
78
+ The options are fetched using the REST-API and the site name is needed for building the URL.
79
+
80
+ ```js
81
+ import router from '@sitevision/api/common/router';
82
+ import { createAppContext } from '@soleil-se/svelte-config/server';
83
+
84
+ router.get('/', (req, res) => {
85
+ createAppContext(res);
86
+ });
87
+ ```
88
+
89
+ ## Example
90
+
91
+ ### Standard
92
+
93
+ ```svelte
94
+ <script>
95
+ import { Panel, RepositoryNodeSelector } from '@soleil-se/svelte-config';
96
+ </script>
97
+
98
+ <Panel heading="Inställningar">
99
+ <RepositoryNodeSelector name="tagGroup" label="Tag group" type="tag-group" />
100
+ <RepositoryNodeSelector name="cookies" label="Cookies" type="site-cookie" multiple />
101
+ </Panel>
102
+ ```
103
+
104
+ ### Advanced
105
+
106
+ ```svelte
107
+ <script>
108
+ import { Panel, RepositoryNodeSelector } from '@soleil-se/svelte-config';
109
+ import { onSave } from '@soleil-se/svelte-config/utils';
110
+
111
+ export let values;
112
+
113
+ values = {
114
+ tagGroup: '',
115
+ ...values
116
+ }
117
+
118
+ onSave(() => values);
119
+
120
+ </script>
121
+
122
+ <Panel heading="Inställningar">
123
+ <RepositoryNodeSelector label="Tag group" type="tag-group" bind:value={values.tagGroup} />
124
+ </Panel>
125
+ ```
126
+
127
+ ## Slots
128
+
129
+ Default slot after selector.
130
+
131
+ ```svelte
132
+ <RepositoryNodeSelector name="tagGroup" label="Tag group" type="tag-group">
133
+ <p class="help-block">Some helpful text.</p>
134
+ </RepositoryNodeSelector>
135
+ ```