@axium/client 0.11.0 → 0.12.1
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/assets/styles.css +5 -1
- package/lib/ZodForm.svelte +29 -0
- package/lib/ZodInput.svelte +265 -0
- package/lib/index.ts +2 -2
- package/package.json +2 -2
- package/lib/Preference.svelte +0 -159
- package/lib/Preferences.svelte +0 -27
package/assets/styles.css
CHANGED
|
@@ -148,7 +148,7 @@ dialog form {
|
|
|
148
148
|
display: contents;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
.error {
|
|
151
|
+
:not(input).error {
|
|
152
152
|
padding: 1em;
|
|
153
153
|
border-radius: 0.5em;
|
|
154
154
|
background-color: var(--bg-error);
|
|
@@ -158,6 +158,10 @@ dialog form {
|
|
|
158
158
|
color: hsl(0 50 50%);
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
input.error {
|
|
162
|
+
border: 1px solid var(--bg-error);
|
|
163
|
+
}
|
|
164
|
+
|
|
161
165
|
.success {
|
|
162
166
|
padding: 1em;
|
|
163
167
|
border-radius: 0.5em;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ZodObject } from 'zod';
|
|
3
|
+
import ZodInput from './ZodInput.svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
rootValue: any;
|
|
7
|
+
schema: ZodObject;
|
|
8
|
+
labels: Record<string, string>;
|
|
9
|
+
updateValue(value: any): void;
|
|
10
|
+
idPrefix?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let { rootValue = $bindable(), schema, labels, updateValue, idPrefix }: Props = $props();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<div class="ZodForm">
|
|
17
|
+
{#each Object.keys(schema.shape).sort((a, b) => a.localeCompare(b)) as path}
|
|
18
|
+
<ZodInput bind:rootValue {updateValue} {idPrefix} {path} schema={schema.shape[path]} label={labels[path] || path} />
|
|
19
|
+
{/each}
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<style>
|
|
23
|
+
.ZodForm {
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
align-items: stretch;
|
|
27
|
+
gap: 1em;
|
|
28
|
+
}
|
|
29
|
+
</style>
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ZodPref } from '@axium/core';
|
|
3
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
4
|
+
import { getByString, pick, setByString } from 'utilium';
|
|
5
|
+
import Icon from './Icon.svelte';
|
|
6
|
+
import ZodInput from './ZodInput.svelte';
|
|
7
|
+
import { prettifyError } from 'zod';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
idPrefix?: string;
|
|
11
|
+
rootValue: any;
|
|
12
|
+
path: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
schema: ZodPref;
|
|
15
|
+
defaultValue?: any;
|
|
16
|
+
optional?: boolean;
|
|
17
|
+
noLabel?: boolean;
|
|
18
|
+
updateValue(value: any): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
rootValue = $bindable(),
|
|
23
|
+
label,
|
|
24
|
+
path,
|
|
25
|
+
schema,
|
|
26
|
+
optional = false,
|
|
27
|
+
defaultValue,
|
|
28
|
+
idPrefix,
|
|
29
|
+
updateValue,
|
|
30
|
+
noLabel = false,
|
|
31
|
+
}: Props = $props();
|
|
32
|
+
const id = (idPrefix ? idPrefix + ':' : '') + path.replaceAll(' ', '_');
|
|
33
|
+
|
|
34
|
+
let value = $state<any>(getByString(rootValue, path));
|
|
35
|
+
|
|
36
|
+
let error = $state();
|
|
37
|
+
|
|
38
|
+
function dateAttr(date: Date | null, format: 'date' | 'time' | 'datetime' | 'time+sec') {
|
|
39
|
+
if (!date) return null;
|
|
40
|
+
|
|
41
|
+
const pad = (n: number) => String(n).padStart(2, '0');
|
|
42
|
+
|
|
43
|
+
const yyyy = date.getFullYear();
|
|
44
|
+
const mm = pad(date.getMonth() + 1);
|
|
45
|
+
const dd = pad(date.getDate());
|
|
46
|
+
const dateStr = `${yyyy}-${mm}-${dd}`;
|
|
47
|
+
|
|
48
|
+
const HH = pad(date.getHours());
|
|
49
|
+
const MM = pad(date.getMinutes());
|
|
50
|
+
const timeStr = `${HH}:${MM}`;
|
|
51
|
+
|
|
52
|
+
switch (format) {
|
|
53
|
+
case 'date':
|
|
54
|
+
return dateStr;
|
|
55
|
+
case 'time':
|
|
56
|
+
return timeStr;
|
|
57
|
+
case 'time+sec':
|
|
58
|
+
return `${timeStr}:${pad(date.getSeconds())}`;
|
|
59
|
+
case 'datetime':
|
|
60
|
+
default:
|
|
61
|
+
return `${dateStr}T${timeStr}`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function onchange(e: Event) {
|
|
66
|
+
let val;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
val = schema.parse(value);
|
|
70
|
+
error = null;
|
|
71
|
+
} catch (e: any) {
|
|
72
|
+
error = prettifyError(e);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (e instanceof KeyboardEvent && e.key !== 'Enter') return;
|
|
77
|
+
|
|
78
|
+
const oldValue = getByString(rootValue, path);
|
|
79
|
+
if (val == oldValue) return;
|
|
80
|
+
|
|
81
|
+
if (defaultValue == val) {
|
|
82
|
+
const parts = path.split('.');
|
|
83
|
+
const prop = parts.pop()!;
|
|
84
|
+
delete getByString<Record<string, any>>(rootValue, parts.join('.'))[prop];
|
|
85
|
+
} else setByString(rootValue, path, val);
|
|
86
|
+
|
|
87
|
+
updateValue(rootValue);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const onkeyup = onchange;
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
{#snippet _in(rest: HTMLInputAttributes)}
|
|
94
|
+
<div class="ZodInput">
|
|
95
|
+
{#if !noLabel}<label for={id}>{label || path}</label>{/if}
|
|
96
|
+
{#if error}<span class="ZodInput-error error-text">{error}</span>{/if}
|
|
97
|
+
<input {id} {...rest} bind:value {onchange} {onkeyup} required={!optional} {defaultValue} class={[error && 'error']} />
|
|
98
|
+
</div>
|
|
99
|
+
{/snippet}
|
|
100
|
+
|
|
101
|
+
{#if schema.type == 'string'}
|
|
102
|
+
{@render _in({ type: schema.format == 'email' ? 'email' : 'text', ...pick(schema, 'minLength', 'maxLength') })}
|
|
103
|
+
{:else if schema.type == 'number'}
|
|
104
|
+
{@render _in({ type: 'number', min: schema.minValue, max: schema.maxValue, step: schema.format?.includes('int') ? 1 : 0.1 })}
|
|
105
|
+
{:else if schema.type == 'bigint'}
|
|
106
|
+
{@render _in({ type: 'number', min: Number(schema.minValue), max: Number(schema.maxValue), step: 1 })}
|
|
107
|
+
{:else if schema.type == 'boolean'}
|
|
108
|
+
<div class="ZodInput">
|
|
109
|
+
{#if !noLabel}<label for="{id}:checkbox">{label || path}</label>{/if}
|
|
110
|
+
<input bind:checked={value} id="{id}:checkbox" type="checkbox" {onchange} {onkeyup} required={!optional} />
|
|
111
|
+
<label for="{id}:checkbox" {id} class="checkbox">
|
|
112
|
+
{#if value}<Icon i="check" --size="1.3em" />{/if}
|
|
113
|
+
</label>
|
|
114
|
+
</div>
|
|
115
|
+
{:else if schema.type == 'date'}
|
|
116
|
+
{@render _in({
|
|
117
|
+
type: 'date',
|
|
118
|
+
min: dateAttr(schema.minDate, 'date'),
|
|
119
|
+
max: dateAttr(schema.maxDate, 'date'),
|
|
120
|
+
})}
|
|
121
|
+
{:else if schema.type == 'file'}
|
|
122
|
+
<!-- todo -->
|
|
123
|
+
{:else if schema.type == 'literal'}
|
|
124
|
+
<div class="ZodInput">
|
|
125
|
+
<label for={id}>{label || path}</label>
|
|
126
|
+
<select bind:value {id} {onchange} {onkeyup} required={!optional}>
|
|
127
|
+
{#each schema.values as value}
|
|
128
|
+
<option {value} selected={value === value}>{value}</option>
|
|
129
|
+
{/each}
|
|
130
|
+
</select>
|
|
131
|
+
</div>
|
|
132
|
+
{:else if schema.type == 'template_literal'}
|
|
133
|
+
<!-- todo -->
|
|
134
|
+
{:else if schema.type == 'default'}
|
|
135
|
+
<ZodInput bind:rootValue {updateValue} {idPrefix} {path} schema={schema.def.innerType} defaultValue={schema.def.defaultValue} />
|
|
136
|
+
{:else if schema.type == 'nullable' || schema.type == 'optional'}
|
|
137
|
+
<!-- defaults are handled differently -->
|
|
138
|
+
<ZodInput bind:rootValue {updateValue} {idPrefix} {path} {defaultValue} schema={schema.def.innerType} optional={true} />
|
|
139
|
+
{:else if schema.type == 'array'}
|
|
140
|
+
<div class="ZodInput">
|
|
141
|
+
{#if !noLabel}<label for={id}>{label || path}</label>{/if}
|
|
142
|
+
{#if error}<span class="ZodInput-error error-text">{error}</span>{/if}
|
|
143
|
+
<div class="ZodInput-array">
|
|
144
|
+
{#each value, i}
|
|
145
|
+
<div class="ZodInput-element">
|
|
146
|
+
<input
|
|
147
|
+
id="{id}.{i}"
|
|
148
|
+
bind:value={value[i]}
|
|
149
|
+
{onchange}
|
|
150
|
+
{onkeyup}
|
|
151
|
+
required={!optional}
|
|
152
|
+
{defaultValue}
|
|
153
|
+
class={[error && 'error']}
|
|
154
|
+
/>
|
|
155
|
+
<button
|
|
156
|
+
onclick={e => {
|
|
157
|
+
value.splice(i, 1);
|
|
158
|
+
onchange(e);
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
<Icon i="trash" --size="16px" />
|
|
162
|
+
</button>
|
|
163
|
+
</div>
|
|
164
|
+
{/each}
|
|
165
|
+
<button onclick={() => value.push('')}>
|
|
166
|
+
<Icon i="plus" --size="16px" />
|
|
167
|
+
</button>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
{:else if schema.type == 'record'}
|
|
171
|
+
<div class="ZodInput-record">
|
|
172
|
+
{#each Object.keys(value) as key}
|
|
173
|
+
<div class="ZodInput-record-entry">
|
|
174
|
+
{#if !noLabel}<label for={id}>{key}</label>{/if}
|
|
175
|
+
<ZodInput bind:rootValue {updateValue} {idPrefix} {defaultValue} path="{path}.{key}" schema={schema.valueType} />
|
|
176
|
+
</div>
|
|
177
|
+
{/each}
|
|
178
|
+
</div>
|
|
179
|
+
{:else if schema.type == 'object'}
|
|
180
|
+
<!-- <div class="ZodInput-object"> -->
|
|
181
|
+
{#each Object.entries(schema.shape) as [key, value]}
|
|
182
|
+
<ZodInput bind:rootValue {updateValue} {idPrefix} {defaultValue} path="{path}.{key}" schema={value} />
|
|
183
|
+
{/each}
|
|
184
|
+
<!-- </div> -->
|
|
185
|
+
{:else if schema.type == 'tuple'}
|
|
186
|
+
<div class="ZodInput-tuple" data-rest={schema.def.rest}>
|
|
187
|
+
{#each schema.def.items as item, i}
|
|
188
|
+
<ZodInput bind:rootValue {updateValue} {idPrefix} {defaultValue} path="{path}.{i}" schema={item} />
|
|
189
|
+
{/each}
|
|
190
|
+
</div>
|
|
191
|
+
{:else if schema.type == 'enum'}
|
|
192
|
+
<div class="ZodInput">
|
|
193
|
+
{#if !noLabel}<label for={id}>{label || path}</label>{/if}
|
|
194
|
+
{#if error}<span class="ZodInput-error error-text">{error}</span>{/if}
|
|
195
|
+
<select {id} {onchange} {onkeyup} bind:value required={!optional}>
|
|
196
|
+
{#each Object.entries(schema.enum) as [key, value]}
|
|
197
|
+
<option {value} selected={value === value}>{key}</option>
|
|
198
|
+
{/each}
|
|
199
|
+
</select>
|
|
200
|
+
</div>
|
|
201
|
+
{:else}
|
|
202
|
+
<!-- No idea how to render this -->
|
|
203
|
+
<i class="error-text">Invalid preference type: {JSON.stringify((schema as ZodPref)?.def?.type)}</i>
|
|
204
|
+
{/if}
|
|
205
|
+
|
|
206
|
+
<style>
|
|
207
|
+
input[type='checkbox'] {
|
|
208
|
+
display: none;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
label.checkbox {
|
|
212
|
+
cursor: pointer;
|
|
213
|
+
width: 1.5em;
|
|
214
|
+
height: 1.5em;
|
|
215
|
+
border: 1px solid var(--border-accent);
|
|
216
|
+
border-radius: 0.5em;
|
|
217
|
+
display: inline-flex;
|
|
218
|
+
justify-content: center;
|
|
219
|
+
align-items: center;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.ZodInput-error {
|
|
223
|
+
position: fixed;
|
|
224
|
+
position-anchor: --zod-input;
|
|
225
|
+
bottom: calc(anchor(top) - 0.3em);
|
|
226
|
+
left: anchor(left);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.ZodInput {
|
|
230
|
+
display: grid;
|
|
231
|
+
grid-template-columns: 1fr 1fr;
|
|
232
|
+
gap: 1em;
|
|
233
|
+
anchor-scope: --zod-input;
|
|
234
|
+
|
|
235
|
+
input {
|
|
236
|
+
anchor-name: --zod-input;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.ZodInput-element {
|
|
241
|
+
display: flex;
|
|
242
|
+
gap: 0.5em;
|
|
243
|
+
align-items: center;
|
|
244
|
+
|
|
245
|
+
button {
|
|
246
|
+
position: relative;
|
|
247
|
+
right: 1em;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.ZodInput-array {
|
|
252
|
+
display: flex;
|
|
253
|
+
gap: 0.5em;
|
|
254
|
+
align-items: center;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.ZodInput-object,
|
|
258
|
+
.ZodInput-record,
|
|
259
|
+
.ZodInput-tuple {
|
|
260
|
+
display: flex;
|
|
261
|
+
flex-direction: column;
|
|
262
|
+
align-items: stretch;
|
|
263
|
+
gap: 0.25em;
|
|
264
|
+
}
|
|
265
|
+
</style>
|
package/lib/index.ts
CHANGED
|
@@ -7,8 +7,6 @@ export { default as Login } from './Login.svelte';
|
|
|
7
7
|
export { default as Logout } from './Logout.svelte';
|
|
8
8
|
export { default as NumberBar } from './NumberBar.svelte';
|
|
9
9
|
export { default as Popover } from './Popover.svelte';
|
|
10
|
-
export { default as Preference } from './Preference.svelte';
|
|
11
|
-
export { default as Preferences } from './Preferences.svelte';
|
|
12
10
|
export { default as Register } from './Register.svelte';
|
|
13
11
|
export { default as SessionList } from './SessionList.svelte';
|
|
14
12
|
export { default as Toast } from './Toast.svelte';
|
|
@@ -18,3 +16,5 @@ export { default as UserCard } from './UserCard.svelte';
|
|
|
18
16
|
export { default as UserMenu } from './UserMenu.svelte';
|
|
19
17
|
export { default as Version } from './Version.svelte';
|
|
20
18
|
export { default as WithContextMenu } from './WithContextMenu.svelte';
|
|
19
|
+
export { default as ZodForm } from './ZodForm.svelte';
|
|
20
|
+
export { default as ZodInput } from './ZodInput.svelte';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axium/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"author": "James Prevett <jp@jamespre.dev>",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"build": "tsc"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@axium/core": ">=0.
|
|
43
|
+
"@axium/core": ">=0.18.0",
|
|
44
44
|
"utilium": "^2.3.8",
|
|
45
45
|
"zod": "^4.0.5",
|
|
46
46
|
"svelte": "^5.36.0"
|
package/lib/Preference.svelte
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { fetchAPI } from '@axium/client/requests';
|
|
3
|
-
import type { Preferences, ZodPref } from '@axium/core';
|
|
4
|
-
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
5
|
-
import { getByString, pick, setByString } from 'utilium';
|
|
6
|
-
import Icon from './Icon.svelte';
|
|
7
|
-
import Preference from './Preference.svelte';
|
|
8
|
-
|
|
9
|
-
interface Props {
|
|
10
|
-
userId: string;
|
|
11
|
-
preferences: Preferences;
|
|
12
|
-
path: string;
|
|
13
|
-
schema: ZodPref;
|
|
14
|
-
defaultValue?: any;
|
|
15
|
-
optional?: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
let { preferences = $bindable(), userId, path, schema, optional = false, defaultValue }: Props = $props();
|
|
19
|
-
const id = $props.id();
|
|
20
|
-
|
|
21
|
-
let input = $state<HTMLInputElement | HTMLSelectElement>()!;
|
|
22
|
-
let checked = $state(schema.def.type == 'boolean' && getByString<boolean>(preferences, path));
|
|
23
|
-
const initialValue = $derived<any>(getByString(preferences, path));
|
|
24
|
-
|
|
25
|
-
function dateAttr(date: Date | null, format: 'date' | 'time' | 'datetime' | 'time+sec') {
|
|
26
|
-
if (!date) return null;
|
|
27
|
-
|
|
28
|
-
const pad = (n: number) => String(n).padStart(2, '0');
|
|
29
|
-
|
|
30
|
-
const yyyy = date.getFullYear();
|
|
31
|
-
const mm = pad(date.getMonth() + 1);
|
|
32
|
-
const dd = pad(date.getDate());
|
|
33
|
-
const dateStr = `${yyyy}-${mm}-${dd}`;
|
|
34
|
-
|
|
35
|
-
const HH = pad(date.getHours());
|
|
36
|
-
const MM = pad(date.getMinutes());
|
|
37
|
-
const timeStr = `${HH}:${MM}`;
|
|
38
|
-
|
|
39
|
-
switch (format) {
|
|
40
|
-
case 'date':
|
|
41
|
-
return dateStr;
|
|
42
|
-
case 'time':
|
|
43
|
-
return timeStr;
|
|
44
|
-
case 'time+sec':
|
|
45
|
-
return `${timeStr}:${pad(date.getSeconds())}`;
|
|
46
|
-
case 'datetime':
|
|
47
|
-
default:
|
|
48
|
-
return `${dateStr}T${timeStr}`;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function onchange(e: Event) {
|
|
53
|
-
const value = schema.parse(input instanceof HTMLInputElement && input.type === 'checkbox' ? input.checked : input.value);
|
|
54
|
-
const oldValue = getByString(preferences, path);
|
|
55
|
-
if (value == oldValue) return;
|
|
56
|
-
|
|
57
|
-
if (defaultValue == value) {
|
|
58
|
-
const parts = path.split('.');
|
|
59
|
-
const prop = parts.pop()!;
|
|
60
|
-
delete getByString<Record<string, any>>(preferences, parts.join('.'))[prop];
|
|
61
|
-
} else setByString(preferences, path, value);
|
|
62
|
-
|
|
63
|
-
fetchAPI('PATCH', 'users/:id', { preferences }, userId);
|
|
64
|
-
}
|
|
65
|
-
</script>
|
|
66
|
-
|
|
67
|
-
{#snippet _in(rest: HTMLInputAttributes)}
|
|
68
|
-
<input bind:this={input} {id} {...rest} value={initialValue} {onchange} required={!optional} {defaultValue} />
|
|
69
|
-
{/snippet}
|
|
70
|
-
|
|
71
|
-
{#if schema.type == 'string'}
|
|
72
|
-
{@render _in({ type: schema.format == 'email' ? 'email' : 'text', ...pick(schema, 'minLength', 'maxLength') })}
|
|
73
|
-
{:else if schema.type == 'number'}
|
|
74
|
-
{@render _in({ type: 'number', min: schema.minValue, max: schema.maxValue, step: schema.format?.includes('int') ? 1 : 0.1 })}
|
|
75
|
-
{:else if schema.type == 'bigint'}
|
|
76
|
-
{@render _in({ type: 'number', min: Number(schema.minValue), max: Number(schema.maxValue), step: 1 })}
|
|
77
|
-
{:else if schema.type == 'boolean'}
|
|
78
|
-
<input bind:checked bind:this={input} {id} type="checkbox" {onchange} required={!optional} />
|
|
79
|
-
<label for={id} class="checkbox">
|
|
80
|
-
{#if checked}<Icon i="check" --size="1.3em" />{/if}
|
|
81
|
-
</label>
|
|
82
|
-
{:else if schema.type == 'date'}
|
|
83
|
-
{@render _in({
|
|
84
|
-
type: 'date',
|
|
85
|
-
min: dateAttr(schema.minDate, 'date'),
|
|
86
|
-
max: dateAttr(schema.maxDate, 'date'),
|
|
87
|
-
})}
|
|
88
|
-
{:else if schema.type == 'file'}
|
|
89
|
-
<!-- todo -->
|
|
90
|
-
{:else if schema.type == 'literal'}
|
|
91
|
-
<select bind:this={input} {id} {onchange} required={!optional}>
|
|
92
|
-
{#each schema.values as value}
|
|
93
|
-
<option {value} selected={initialValue === value}>{value}</option>
|
|
94
|
-
{/each}
|
|
95
|
-
</select>
|
|
96
|
-
{:else if schema.type == 'template_literal'}
|
|
97
|
-
<!-- todo -->
|
|
98
|
-
{:else if schema.type == 'default'}
|
|
99
|
-
<Preference {userId} bind:preferences {path} schema={schema.def.innerType} defaultValue={schema.def.defaultValue} />
|
|
100
|
-
{:else if schema.type == 'nullable' || schema.type == 'optional'}
|
|
101
|
-
<!-- defaults are handled differently -->
|
|
102
|
-
<Preference {userId} bind:preferences {path} {defaultValue} schema={schema.def.innerType} optional={true} />
|
|
103
|
-
{:else if schema.type == 'array'}
|
|
104
|
-
<div class="pref-sub">
|
|
105
|
-
{#each initialValue, i}
|
|
106
|
-
<div class="pref-record-entry">
|
|
107
|
-
<Preference {userId} bind:preferences {defaultValue} path="{path}.{i}" schema={schema.element} />
|
|
108
|
-
</div>
|
|
109
|
-
{/each}
|
|
110
|
-
</div>
|
|
111
|
-
{:else if schema.type == 'record'}
|
|
112
|
-
<div class="pref-sub">
|
|
113
|
-
{#each Object.keys(initialValue) as key}
|
|
114
|
-
<div class="pref-record-entry">
|
|
115
|
-
<label for={id}>{key}</label>
|
|
116
|
-
<Preference {userId} bind:preferences {defaultValue} path="{path}.{key}" schema={schema.valueType} />
|
|
117
|
-
</div>
|
|
118
|
-
{/each}
|
|
119
|
-
</div>
|
|
120
|
-
{:else if schema.type == 'object'}
|
|
121
|
-
{#each Object.entries(schema.shape) as [key, value]}
|
|
122
|
-
<div class="pref-sub">
|
|
123
|
-
<label for={id}>{key}</label>
|
|
124
|
-
<Preference {userId} bind:preferences {defaultValue} path="{path}.{key}" schema={value} />
|
|
125
|
-
</div>
|
|
126
|
-
{/each}
|
|
127
|
-
{:else if schema.type == 'tuple'}
|
|
128
|
-
<div class="pref-sub" data-rest={schema.def.rest}>
|
|
129
|
-
{#each schema.def.items as item, i}
|
|
130
|
-
<Preference {userId} bind:preferences {defaultValue} path="{path}.{i}" schema={item} />
|
|
131
|
-
{/each}
|
|
132
|
-
</div>
|
|
133
|
-
{:else if schema.type == 'enum'}
|
|
134
|
-
<select bind:this={input} {id} {onchange} required={!optional}>
|
|
135
|
-
{#each Object.entries(schema.enum) as [key, value]}
|
|
136
|
-
<option {value} selected={initialValue === value}>{key}</option>
|
|
137
|
-
{/each}
|
|
138
|
-
</select>
|
|
139
|
-
{:else}
|
|
140
|
-
<!-- No idea how to render this -->
|
|
141
|
-
<i class="error-text">Invalid preference type: {JSON.stringify((schema as ZodPref)?.def?.type)}</i>
|
|
142
|
-
{/if}
|
|
143
|
-
|
|
144
|
-
<style>
|
|
145
|
-
input[type='checkbox'] {
|
|
146
|
-
display: none;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
label.checkbox {
|
|
150
|
-
cursor: pointer;
|
|
151
|
-
width: 1.5em;
|
|
152
|
-
height: 1.5em;
|
|
153
|
-
border: 1px solid var(--border-accent);
|
|
154
|
-
border-radius: 0.5em;
|
|
155
|
-
display: inline-flex;
|
|
156
|
-
justify-content: center;
|
|
157
|
-
align-items: center;
|
|
158
|
-
}
|
|
159
|
-
</style>
|
package/lib/Preferences.svelte
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { preferenceLabels, Preferences } from '@axium/core';
|
|
3
|
-
import Preference from './Preference.svelte';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
userId: string;
|
|
7
|
-
preferences: Preferences;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
let { preferences = $bindable(), userId }: Props = $props();
|
|
11
|
-
const id = $props.id();
|
|
12
|
-
</script>
|
|
13
|
-
|
|
14
|
-
{#each Object.keys(Preferences.shape) as (keyof Preferences)[] as path}
|
|
15
|
-
<div class="pref">
|
|
16
|
-
<label for={id}>{preferenceLabels[path]}</label>
|
|
17
|
-
<Preference {userId} bind:preferences {path} schema={Preferences.shape[path]} />
|
|
18
|
-
</div>
|
|
19
|
-
{/each}
|
|
20
|
-
|
|
21
|
-
<style>
|
|
22
|
-
.pref {
|
|
23
|
-
display: grid;
|
|
24
|
-
grid-template-columns: 1fr 1fr;
|
|
25
|
-
gap: 1em;
|
|
26
|
-
}
|
|
27
|
-
</style>
|