@rokkit/core 1.0.0-next.11
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/LICENSE +21 -0
- package/README.md +1 -0
- package/package.json +59 -0
- package/src/Accordion.svelte +80 -0
- package/src/Alerts.svelte +39 -0
- package/src/DropDown.svelte +79 -0
- package/src/DropSearch.svelte +67 -0
- package/src/Icon.svelte +15 -0
- package/src/List-Discard.svelte +48 -0
- package/src/List.svelte +65 -0
- package/src/ListActions.svelte +35 -0
- package/src/NavTabs.svelte +0 -0
- package/src/NestedList.svelte +87 -0
- package/src/Overlay.svelte +4 -0
- package/src/PageNavigator.svelte +94 -0
- package/src/ResponsiveGrid.svelte +73 -0
- package/src/Scrollable.svelte +8 -0
- package/src/Searchable.svelte +19 -0
- package/src/Sidebar.svelte +5 -0
- package/src/Slider.svelte +17 -0
- package/src/SpinList.svelte +48 -0
- package/src/SplitPane.svelte +109 -0
- package/src/SplitView.svelte +44 -0
- package/src/Splitter.svelte +95 -0
- package/src/TabItem.svelte +27 -0
- package/src/TabItems.svelte +34 -0
- package/src/Tabs.svelte +31 -0
- package/src/Tree.svelte +19 -0
- package/src/actions/dismissable.js +24 -0
- package/src/actions/fillable.js +114 -0
- package/src/actions/hierarchy.js +189 -0
- package/src/actions/index.js +7 -0
- package/src/actions/navigable.js +42 -0
- package/src/actions/navigator.js +179 -0
- package/src/actions/pannable.js +50 -0
- package/src/actions/swipeable.js +56 -0
- package/src/actions/themeable.js +23 -0
- package/src/constants.js +149 -0
- package/src/index.js +27 -0
- package/src/items/Collapsible.svelte +51 -0
- package/src/items/Connector.svelte +26 -0
- package/src/items/Link.svelte +17 -0
- package/src/items/Node.svelte +52 -0
- package/src/items/Pill.svelte +19 -0
- package/src/items/Separator.svelte +1 -0
- package/src/items/Summary.svelte +27 -0
- package/src/items/Text.svelte +21 -0
- package/src/items/index.js +8 -0
- package/src/list.js +14 -0
- package/src/mocks/Custom.svelte +7 -0
- package/src/mocks/index.js +10 -0
- package/src/stores/alerts.js +3 -0
- package/src/stores/index.js +6 -0
- package/src/stores/persist.js +63 -0
- package/src/stores/theme.js +34 -0
package/src/constants.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
export const defaultIcons = [
|
|
2
|
+
'accordion-opened',
|
|
3
|
+
'accordion-closed',
|
|
4
|
+
'action-remove',
|
|
5
|
+
'action-add',
|
|
6
|
+
'action-clear',
|
|
7
|
+
'action-search',
|
|
8
|
+
'action-close',
|
|
9
|
+
'action-close-filled',
|
|
10
|
+
'node-opened',
|
|
11
|
+
'node-closed',
|
|
12
|
+
'selector-opened',
|
|
13
|
+
'selector-closed',
|
|
14
|
+
'checkbox-checked',
|
|
15
|
+
'checkbox-unchecked',
|
|
16
|
+
'checkbox-unknown',
|
|
17
|
+
'rating-filled',
|
|
18
|
+
'rating-empty',
|
|
19
|
+
'radio-off',
|
|
20
|
+
'radio-on',
|
|
21
|
+
'mode-dark',
|
|
22
|
+
'mode-light',
|
|
23
|
+
'navigate-left',
|
|
24
|
+
'navigate-right',
|
|
25
|
+
'navigate-up',
|
|
26
|
+
'navigate-down'
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
// export const defaultIcons = [
|
|
30
|
+
// 'accordion-opened',
|
|
31
|
+
// 'accordion-closed',
|
|
32
|
+
// 'action-remove',
|
|
33
|
+
// 'action-add',
|
|
34
|
+
// 'action-clear',
|
|
35
|
+
// 'action-search',
|
|
36
|
+
// 'action-close',
|
|
37
|
+
// 'node-opened',
|
|
38
|
+
// 'node-closed',
|
|
39
|
+
// 'checkbox-checked',
|
|
40
|
+
// 'checkbox-unchecked',
|
|
41
|
+
// 'checkbox-unknown',
|
|
42
|
+
// 'rating-filled',
|
|
43
|
+
// 'rating-empty',
|
|
44
|
+
// 'rating-half',
|
|
45
|
+
// 'radio-off',
|
|
46
|
+
// 'radio-on',
|
|
47
|
+
// 'folder-closed',
|
|
48
|
+
// 'folder-opened',
|
|
49
|
+
// 'navigate-up',
|
|
50
|
+
// 'navigate-down',
|
|
51
|
+
// 'navigate-left',
|
|
52
|
+
// 'navigate-right',
|
|
53
|
+
// 'selector-closed',
|
|
54
|
+
// 'selector-opened',
|
|
55
|
+
// 'mode-dark',
|
|
56
|
+
// 'mode-light'
|
|
57
|
+
// ]
|
|
58
|
+
|
|
59
|
+
export const defaultOptions = {
|
|
60
|
+
id: 'id',
|
|
61
|
+
label: 'label',
|
|
62
|
+
value: 'value',
|
|
63
|
+
checked: 'checked'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Structure to map custom fields for rendering
|
|
68
|
+
*
|
|
69
|
+
* @typedef FieldMapping
|
|
70
|
+
* @property {string} [id='id'] - Unique id for the item
|
|
71
|
+
* @property {string} [text='text'] - Attribute to identify the text to render
|
|
72
|
+
* @property {string} [url='url'] - Attribute to identify a URL
|
|
73
|
+
* @property {string} [icon='icon'] - Attribute to identify an icon class to render
|
|
74
|
+
* @property {string} [image='image'] - Attribute to identify an image to render
|
|
75
|
+
* @property {string} [children='children'] - Attribute to identify children of the current item
|
|
76
|
+
* @property {string} [summary='summary']
|
|
77
|
+
* @property {string} [notes='notes']
|
|
78
|
+
* @property {string} [props='props']
|
|
79
|
+
* @property {string} [isOpen='_open'] - Attribute to identify if the current item is open
|
|
80
|
+
* @property {string} [isDeleted='_deleted'] - Attribute to identify if the current item is deleted
|
|
81
|
+
* @property {FieldMapping} [fields] - Field mapping to be used on children in the next level
|
|
82
|
+
*/
|
|
83
|
+
export const defaultFields = {
|
|
84
|
+
id: 'id',
|
|
85
|
+
url: 'url',
|
|
86
|
+
text: 'text',
|
|
87
|
+
children: 'data',
|
|
88
|
+
icon: 'icon',
|
|
89
|
+
image: 'image',
|
|
90
|
+
component: 'component',
|
|
91
|
+
summary: 'summary',
|
|
92
|
+
notes: 'notes',
|
|
93
|
+
props: 'props',
|
|
94
|
+
target: 'target',
|
|
95
|
+
isOpen: '_open',
|
|
96
|
+
isDeleted: '_deleted'
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const defaultKeyMap = {
|
|
100
|
+
ArrowRight: 'open',
|
|
101
|
+
ArrowLeft: 'close',
|
|
102
|
+
ArrowDown: 'down',
|
|
103
|
+
ArrowUp: 'up',
|
|
104
|
+
Enter: 'select',
|
|
105
|
+
Escape: 'deselect'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function stateIconsFromNames(icons) {
|
|
109
|
+
return icons
|
|
110
|
+
.map((k) => [...k.split('-')])
|
|
111
|
+
.reduce(
|
|
112
|
+
(acc, parts) => ({
|
|
113
|
+
...acc,
|
|
114
|
+
[parts[0]]: { ...acc[parts[0]], [parts[1]]: parts.join('-') }
|
|
115
|
+
}),
|
|
116
|
+
{}
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const defaultStateIcons = stateIconsFromNames(defaultIcons)
|
|
121
|
+
// export const defaultStateIcons = {
|
|
122
|
+
// accordion: {
|
|
123
|
+
// opened: 'accordion-opened',
|
|
124
|
+
// closed: 'accordion-closed'
|
|
125
|
+
// },
|
|
126
|
+
// item: {
|
|
127
|
+
// remove: 'item-remove',
|
|
128
|
+
// add: 'item-add',
|
|
129
|
+
// clear: 'item-clear',
|
|
130
|
+
// search: 'item-search'
|
|
131
|
+
// },
|
|
132
|
+
// node: {
|
|
133
|
+
// opened: 'node-opened',
|
|
134
|
+
// closed: 'node-closed'
|
|
135
|
+
// },
|
|
136
|
+
// list: {
|
|
137
|
+
// selector: 'list-selector'
|
|
138
|
+
// },
|
|
139
|
+
// checkbox: {
|
|
140
|
+
// checked: 'checkbox-checked',
|
|
141
|
+
// unchecked: 'checkbox-unchecked',
|
|
142
|
+
// unknown: 'checkbox-unknown'
|
|
143
|
+
// },
|
|
144
|
+
// rating: { filled: 'rating-filled', empty: 'rating-empty' },
|
|
145
|
+
// radio: {
|
|
146
|
+
// off: 'radio-off',
|
|
147
|
+
// on: 'radio-on'
|
|
148
|
+
// }
|
|
149
|
+
// }
|
package/src/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export * from './constants'
|
|
2
|
+
export * from './items'
|
|
3
|
+
|
|
4
|
+
export { default as Icon } from './Icon.svelte'
|
|
5
|
+
// export { default as List } from './List-Discard.svelte'
|
|
6
|
+
export { default as Tree } from './Tree.svelte'
|
|
7
|
+
export { default as Accordion } from './Accordion.svelte'
|
|
8
|
+
export { default as List } from './List.svelte'
|
|
9
|
+
export { default as Searchable } from './Searchable.svelte'
|
|
10
|
+
export { default as Scrollable } from './Scrollable.svelte'
|
|
11
|
+
export { default as NestedList } from './NestedList.svelte'
|
|
12
|
+
export { default as Tabs } from './Tabs.svelte'
|
|
13
|
+
|
|
14
|
+
export { default as Alerts } from './Alerts.svelte'
|
|
15
|
+
export { default as Slider } from './Slider.svelte'
|
|
16
|
+
export { default as DropDown } from './DropDown.svelte'
|
|
17
|
+
export { default as DropSearch } from './DropSearch.svelte'
|
|
18
|
+
export { default as SpinList } from './SpinList.svelte'
|
|
19
|
+
|
|
20
|
+
export { default as Sidebar } from './Sidebar.svelte'
|
|
21
|
+
export { default as SplitPane } from './SplitPane.svelte'
|
|
22
|
+
export { default as Splitter } from './Splitter.svelte'
|
|
23
|
+
export { default as SplitView } from './SplitView.svelte'
|
|
24
|
+
export { default as Overlay } from './Overlay.svelte'
|
|
25
|
+
|
|
26
|
+
export { default as PageNavigator } from './PageNavigator.svelte'
|
|
27
|
+
export { default as ResponsiveGrid } from './ResponsiveGrid.svelte'
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { createEventDispatcher } from 'svelte'
|
|
3
|
+
import { defaultFields } from '../constants'
|
|
4
|
+
|
|
5
|
+
const dispatch = createEventDispatcher()
|
|
6
|
+
|
|
7
|
+
export let content
|
|
8
|
+
export let fields = {}
|
|
9
|
+
|
|
10
|
+
$: fields = { ...defaultFields, ...fields }
|
|
11
|
+
$: hasItems = content[fields.children] && content[fields.children].length > 0
|
|
12
|
+
|
|
13
|
+
function toggle() {
|
|
14
|
+
if (hasItems) {
|
|
15
|
+
content.isOpen = !content.isOpen
|
|
16
|
+
}
|
|
17
|
+
dispatch('toggle', content)
|
|
18
|
+
}
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
22
|
+
<collapsible
|
|
23
|
+
class="flex flex-row flex-shrink-0 items-center w-full leading-loose cursor-pointer"
|
|
24
|
+
class:expanded={content.isOpen}
|
|
25
|
+
on:click={toggle}
|
|
26
|
+
>
|
|
27
|
+
{#if content[fields.image]}
|
|
28
|
+
<img
|
|
29
|
+
class="h-8 w-8 rounded-full"
|
|
30
|
+
alt={content[fields.text]}
|
|
31
|
+
src={content[fields.image]}
|
|
32
|
+
/>
|
|
33
|
+
{/if}
|
|
34
|
+
{#if content[fields.icon]}
|
|
35
|
+
<icon class={content[fields.icon]} />
|
|
36
|
+
{/if}
|
|
37
|
+
{#if content[fields.url]}
|
|
38
|
+
<a href={content[fields.url]} class="flex flex-grow">
|
|
39
|
+
{content[fields.text]}
|
|
40
|
+
</a>
|
|
41
|
+
{:else}
|
|
42
|
+
<p class="flex flex-grow">{content[fields.text]}</p>
|
|
43
|
+
{/if}
|
|
44
|
+
{#if hasItems}
|
|
45
|
+
{#if content.isOpen}
|
|
46
|
+
<icon class="sm accordion-opened" aria-label="expand" />
|
|
47
|
+
{:else}
|
|
48
|
+
<icon class="sm accordion-closed" aria-label="collapse" />
|
|
49
|
+
{/if}
|
|
50
|
+
{/if}
|
|
51
|
+
</collapsible>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export let type
|
|
3
|
+
export let rtl = false
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<span class="grid grid-rows-2 grid-cols-2 h-full w-4">
|
|
7
|
+
{#if type === 'last'}
|
|
8
|
+
{#if rtl}
|
|
9
|
+
<i class="border-b border-r" />
|
|
10
|
+
{:else}
|
|
11
|
+
<i class="border-r" />
|
|
12
|
+
<i class="border-b" />
|
|
13
|
+
{/if}
|
|
14
|
+
{:else if type === 'middle'}
|
|
15
|
+
{#if rtl}
|
|
16
|
+
<i class="border-r grid grid-rows-2 row-span-2">
|
|
17
|
+
<i class="border-b" />
|
|
18
|
+
</i>
|
|
19
|
+
{:else}
|
|
20
|
+
<i class="border-r col-span-1 row-span-2" />
|
|
21
|
+
<i class="border-b" />
|
|
22
|
+
{/if}
|
|
23
|
+
{:else if type === 'line'}
|
|
24
|
+
<i class="border-r row-span-2" />
|
|
25
|
+
{/if}
|
|
26
|
+
</span>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Text from './Text.svelte'
|
|
3
|
+
import { defaultFields } from '../constants'
|
|
4
|
+
|
|
5
|
+
export let content
|
|
6
|
+
export let fields = defaultFields
|
|
7
|
+
|
|
8
|
+
$: target = fields.target ? content[fields.target] : ''
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<a
|
|
12
|
+
class="flex flex-row flex-grow gap-2 items-center content"
|
|
13
|
+
href={content[fields.url]}
|
|
14
|
+
{target}
|
|
15
|
+
>
|
|
16
|
+
<Text {content} {fields} />
|
|
17
|
+
</a>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Connector from './Connector.svelte'
|
|
3
|
+
import { defaultFields, defaultStateIcons } from '../constants'
|
|
4
|
+
import { createEventDispatcher } from 'svelte'
|
|
5
|
+
|
|
6
|
+
const dispatch = createEventDispatcher()
|
|
7
|
+
export let content
|
|
8
|
+
export let fields = defaultFields
|
|
9
|
+
export let types = []
|
|
10
|
+
export let stateIcons = defaultStateIcons.node
|
|
11
|
+
export let linesVisible = true
|
|
12
|
+
export let selected = false
|
|
13
|
+
export let using = {}
|
|
14
|
+
export let rtl = false
|
|
15
|
+
export let path = []
|
|
16
|
+
|
|
17
|
+
$: hasChildren = fields.children in content
|
|
18
|
+
$: state =
|
|
19
|
+
hasChildren && content[fields.isOpen]
|
|
20
|
+
? { icon: stateIcons.opened, label: 'collapse' }
|
|
21
|
+
: { icon: stateIcons.closed, label: 'expand' }
|
|
22
|
+
$: component = content[fields.component]
|
|
23
|
+
? using[content[fields.component]] || using.default
|
|
24
|
+
: using.default
|
|
25
|
+
|
|
26
|
+
// function toggle() {
|
|
27
|
+
// if (hasChildren) content[fields.isOpen] = !content[fields.isOpen]
|
|
28
|
+
// dispatch('select', content)
|
|
29
|
+
// }
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
33
|
+
<node
|
|
34
|
+
id={'id-' + path.join('-')}
|
|
35
|
+
class="flex flex-row h-8 gap-2 leading-loose items-center cursor-pointer select-none"
|
|
36
|
+
class:is-selected={selected}
|
|
37
|
+
aria-selected={selected}
|
|
38
|
+
role="option"
|
|
39
|
+
data-path={path.join(',')}
|
|
40
|
+
>
|
|
41
|
+
{#each types.slice(1) as type}
|
|
42
|
+
<Connector type={linesVisible ? type : 'empty'} />
|
|
43
|
+
{/each}
|
|
44
|
+
|
|
45
|
+
{#if hasChildren}
|
|
46
|
+
<span class="flex flex-col w-4 h-full items-center justify-center">
|
|
47
|
+
<icon class={state.icon} aria-label={state.label} tabindex="-1" />
|
|
48
|
+
</span>
|
|
49
|
+
{/if}
|
|
50
|
+
|
|
51
|
+
<svelte:component this={component} bind:content />
|
|
52
|
+
</node>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { createEventDispatcher } from 'svelte'
|
|
3
|
+
import { defaultStateIcons } from '../constants'
|
|
4
|
+
|
|
5
|
+
const dispatch = createEventDispatcher()
|
|
6
|
+
|
|
7
|
+
export let item
|
|
8
|
+
export let fields
|
|
9
|
+
|
|
10
|
+
function handleClick() {
|
|
11
|
+
dispatch('remove', item)
|
|
12
|
+
}
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<pill class="flex flex-row items-center">
|
|
16
|
+
{item[fields.text]}
|
|
17
|
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
18
|
+
<icon class={defaultStateIcons.item.remove} on:click={handleClick} />
|
|
19
|
+
</pill>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<span class="my-4 flex flex-grow separator" />
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { defaultFields } from '../constants'
|
|
3
|
+
import Text from './Text.svelte'
|
|
4
|
+
|
|
5
|
+
export let content
|
|
6
|
+
export let fields = {}
|
|
7
|
+
export let using = {}
|
|
8
|
+
|
|
9
|
+
$: fields = { ...defaultFields, ...fields }
|
|
10
|
+
$: using = { default: Text, ...using }
|
|
11
|
+
$: hasItems = content[fields.children] && content[fields.children].length > 0
|
|
12
|
+
$: component = using[content[fields.component] ?? 'default']
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<summary
|
|
16
|
+
class="flex flex-row flex-shrink-0 items-center w-full cursor-pointer"
|
|
17
|
+
tabindex="-1"
|
|
18
|
+
>
|
|
19
|
+
<svelte:component this={component} bind:content {fields} />
|
|
20
|
+
{#if hasItems}
|
|
21
|
+
{#if content[fields.isOpen]}
|
|
22
|
+
<icon class="sm accordion-opened" aria-label="expand" />
|
|
23
|
+
{:else}
|
|
24
|
+
<icon class="sm accordion-closed" aria-label="collapse" />
|
|
25
|
+
{/if}
|
|
26
|
+
{/if}
|
|
27
|
+
</summary>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { defaultFields } from '../constants'
|
|
3
|
+
|
|
4
|
+
export let content
|
|
5
|
+
export let fields = defaultFields
|
|
6
|
+
|
|
7
|
+
$: isObject = typeof content == 'object'
|
|
8
|
+
$: text = isObject ? content[fields.text] : content
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
{#if isObject && content[fields.image]}
|
|
12
|
+
<img
|
|
13
|
+
class="h-8 w-8 rounded-full object-cover"
|
|
14
|
+
alt={content[fields.text]}
|
|
15
|
+
src={content[fields.image]}
|
|
16
|
+
/>
|
|
17
|
+
{/if}
|
|
18
|
+
{#if isObject && content[fields.icon]}
|
|
19
|
+
<icon class={content[fields.icon]} />
|
|
20
|
+
{/if}
|
|
21
|
+
<p class="flex flex-grow">{text}</p>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { default as Text } from './Text.svelte'
|
|
2
|
+
export { default as Link } from './Link.svelte'
|
|
3
|
+
export { default as Node } from './Node.svelte'
|
|
4
|
+
export { default as Pill } from './Pill.svelte'
|
|
5
|
+
export { default as Collapsible } from './Collapsible.svelte'
|
|
6
|
+
export { default as Connector } from './Connector.svelte'
|
|
7
|
+
export { default as Separator } from './Separator.svelte'
|
|
8
|
+
export { default as Summary } from './Summary.svelte'
|
package/src/list.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function getComponent(item, fields) {
|
|
2
|
+
if (item && typeof item === 'object') {
|
|
3
|
+
return item[fields.component] || fields.default
|
|
4
|
+
}
|
|
5
|
+
return fields.default
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// update position based on externally supplied value
|
|
9
|
+
export function updateCursor(cursor, value, items) {
|
|
10
|
+
if (cursor.length > 0 && value != items[cursor[0]]) {
|
|
11
|
+
let index = items.findIndex((x) => x == value)
|
|
12
|
+
cursor = index > -1 ? [index] : []
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export const PARSE_ERROR_MESSAGE =
|
|
2
|
+
'Unable to parse value from local storage for key: '
|
|
3
|
+
|
|
4
|
+
if (typeof window === 'undefined') {
|
|
5
|
+
global.localStorage = {
|
|
6
|
+
getItem: () => '{}',
|
|
7
|
+
setItem: () => {}
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function persistable(key, store) {
|
|
11
|
+
let value
|
|
12
|
+
const storageEventListener = (event) => {
|
|
13
|
+
if (event.key === key) {
|
|
14
|
+
event.stopPropagation()
|
|
15
|
+
event.preventDefault()
|
|
16
|
+
try {
|
|
17
|
+
const newValue = JSON.parse(event.newValue)
|
|
18
|
+
set(newValue)
|
|
19
|
+
} catch (e) {
|
|
20
|
+
console.error(PARSE_ERROR_MESSAGE, key)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
value = JSON.parse(localStorage.getItem(key))
|
|
27
|
+
// console.log(value)
|
|
28
|
+
store.set(value)
|
|
29
|
+
} catch {
|
|
30
|
+
console.error(PARSE_ERROR_MESSAGE, key)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const set = (newValue) => {
|
|
34
|
+
if (value !== newValue) {
|
|
35
|
+
value = newValue
|
|
36
|
+
localStorage.setItem(key, JSON.stringify(value))
|
|
37
|
+
store.set(value)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const update = (fn) => {
|
|
42
|
+
store.update((currentValue) => {
|
|
43
|
+
const value = fn(currentValue)
|
|
44
|
+
localStorage.setItem(key, JSON.stringify(value))
|
|
45
|
+
return value
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (typeof window !== 'undefined') {
|
|
50
|
+
window.addEventListener('storage', storageEventListener)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
subscribe: store.subscribe,
|
|
55
|
+
set,
|
|
56
|
+
update,
|
|
57
|
+
destroy() {
|
|
58
|
+
if (typeof window !== 'undefined') {
|
|
59
|
+
window.removeEventListener('storage', storageEventListener)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { writable } from 'svelte/store'
|
|
2
|
+
import { persistable } from './persist'
|
|
3
|
+
|
|
4
|
+
const THEME_STORE_KEY = 'app-theme'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {Object} Theme
|
|
8
|
+
* @property {string} name
|
|
9
|
+
* @property {string} mode
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Custom store that takes an object with two properties, name and mode.
|
|
14
|
+
* @returns {import('svelte/store').Writable<Theme>}
|
|
15
|
+
*/
|
|
16
|
+
export function ThemeStore() {
|
|
17
|
+
const store = writable({ name: 'rokkit', mode: 'dark' })
|
|
18
|
+
|
|
19
|
+
const set = (value) => {
|
|
20
|
+
const { name, mode } = value ?? {}
|
|
21
|
+
if (typeof name === 'string' && typeof mode === 'string') {
|
|
22
|
+
store.set(value)
|
|
23
|
+
} else if (value) {
|
|
24
|
+
console.error('Both "name" and "mode" must be strings', value)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
...store,
|
|
30
|
+
set
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const theme = persistable(THEME_STORE_KEY, ThemeStore())
|