@axium/client 0.36.7 → 0.37.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/dist/cli/cache.d.ts +1 -0
- package/dist/cli/cache.js +3 -14
- package/dist/cli/config.d.ts +1 -0
- package/dist/cli/config.js +2 -0
- package/dist/cli/sync.d.ts +2 -5
- package/dist/cli/sync.js +2 -4
- package/dist/locales.d.ts +3 -270
- package/dist/locales.js +5 -17
- package/dist/socket.js +4 -5
- package/dist/sync.d.ts +22 -0
- package/dist/sync.js +67 -0
- package/dist/themes.d.ts +2 -0
- package/dist/themes.js +34 -0
- package/dist/user.d.ts +1 -0
- package/dist/user.js +5 -0
- package/dist/web/features.d.ts +17 -0
- package/dist/web/features.js +47 -0
- package/dist/web/hooks.d.ts +21 -0
- package/dist/web/hooks.js +33 -0
- package/dist/{gui → web}/index.d.ts +4 -0
- package/dist/{gui → web}/index.js +4 -0
- package/dist/web/locales.d.ts +6 -0
- package/dist/web/locales.js +21 -0
- package/dist/web/pwa.d.ts +44 -0
- package/dist/web/pwa.js +138 -0
- package/dist/web/service-worker.d.ts +1 -0
- package/dist/web/service-worker.js +66 -0
- package/dist/web/utils.d.ts +1 -0
- package/dist/web/utils.js +4 -0
- package/lib/ClipboardCopy.svelte +1 -1
- package/lib/PWAIndicator.svelte +22 -0
- package/lib/ZodForm.svelte +10 -1
- package/lib/ZodInput.svelte +4 -1
- package/lib/attachments/context-menu.ts +1 -1
- package/lib/attachments/selection.ts +1 -1
- package/lib/index.ts +1 -0
- package/lib/reactive/index.svelte.ts +1 -0
- package/lib/reactive/sync.svelte.ts +45 -0
- package/lib/toast.ts +1 -1
- package/lib/tsconfig.json +1 -1
- package/locales/en.json +11 -1
- package/package.json +3 -3
- /package/dist/{gui → web}/animate.d.ts +0 -0
- /package/dist/{gui → web}/animate.js +0 -0
- /package/dist/{gui → web}/clipboard.d.ts +0 -0
- /package/dist/{gui → web}/clipboard.js +0 -0
- /package/dist/{gui → web}/controls.d.ts +0 -0
- /package/dist/{gui → web}/controls.js +0 -0
- /package/dist/{gui → web}/mobile.d.ts +0 -0
- /package/dist/{gui → web}/mobile.js +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare function init(): Promise<void>;
|
|
2
|
+
interface SK_ErrorData {
|
|
3
|
+
error: Error & {
|
|
4
|
+
status?: number;
|
|
5
|
+
};
|
|
6
|
+
event: {
|
|
7
|
+
params: Record<string, unknown>;
|
|
8
|
+
route: {
|
|
9
|
+
id: string;
|
|
10
|
+
};
|
|
11
|
+
url: URL;
|
|
12
|
+
};
|
|
13
|
+
status: number;
|
|
14
|
+
message: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function handleError({ error, status }: SK_ErrorData): {
|
|
17
|
+
message: string;
|
|
18
|
+
stack: string | undefined;
|
|
19
|
+
status: number;
|
|
20
|
+
};
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import feature from '@axium/core/features';
|
|
2
|
+
import { errorText } from 'ioium';
|
|
3
|
+
import { themeStyles } from '../themes.js';
|
|
4
|
+
import { extendCurrentSession, getCurrentSession } from '../user.js';
|
|
5
|
+
import { loadFeatures } from './features.js';
|
|
6
|
+
import { loadLocale } from './locales.js';
|
|
7
|
+
import * as pwa from './pwa.js';
|
|
8
|
+
const day = 86400_000;
|
|
9
|
+
export async function init() {
|
|
10
|
+
await loadLocale().catch(() => { });
|
|
11
|
+
const session = await getCurrentSession().catch(() => null);
|
|
12
|
+
await loadFeatures(session?.userId).catch(() => { });
|
|
13
|
+
if (session && session.expires.getTime() < Date.now() + day)
|
|
14
|
+
try {
|
|
15
|
+
await extendCurrentSession(session.userId);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
console.debug('Failed to extend current session');
|
|
19
|
+
}
|
|
20
|
+
if (feature('pwa'))
|
|
21
|
+
void pwa.register().catch(e => console.debug('Failed to register service worker:', errorText(e)));
|
|
22
|
+
else
|
|
23
|
+
void pwa.unregister().catch(() => { });
|
|
24
|
+
if (feature('themes')) {
|
|
25
|
+
const theme = themeStyles[session?.user?.preferences?.theme || 'default'] || {};
|
|
26
|
+
for (const [prop, value] of Object.entries(theme))
|
|
27
|
+
document.documentElement.style.setProperty('--' + prop, value);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function handleError({ error, status }) {
|
|
31
|
+
console.error(error);
|
|
32
|
+
return { message: errorText(error), stack: error.stack, status: error.status || status };
|
|
33
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export * from './animate.js';
|
|
2
2
|
export * from './clipboard.js';
|
|
3
3
|
export * as controls from './controls.js';
|
|
4
|
+
export * from './features.js';
|
|
5
|
+
export * from './locales.js';
|
|
4
6
|
export * from './mobile.js';
|
|
7
|
+
export * as pwa from './pwa.js';
|
|
8
|
+
export * from './utils.js';
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export * from './animate.js';
|
|
2
2
|
export * from './clipboard.js';
|
|
3
3
|
export * as controls from './controls.js';
|
|
4
|
+
export * from './features.js';
|
|
5
|
+
export * from './locales.js';
|
|
4
6
|
export * from './mobile.js';
|
|
7
|
+
export * as pwa from './pwa.js';
|
|
8
|
+
export * from './utils.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch the translations the server has for a language and start using them.
|
|
3
|
+
* The server resolves the language, so for example asking for `en-US` may give you `en`.
|
|
4
|
+
* @returns the locale that is now in use
|
|
5
|
+
*/
|
|
6
|
+
export declare function loadLocale(language?: string): Promise<string>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { extendLocale } from '@axium/core/locales';
|
|
2
|
+
import { debug, warn } from 'ioium';
|
|
3
|
+
import { currentLocale, useLocale } from '../locales.js';
|
|
4
|
+
import { origin } from '../requests.js';
|
|
5
|
+
/**
|
|
6
|
+
* Fetch the translations the server has for a language and start using them.
|
|
7
|
+
* The server resolves the language, so for example asking for `en-US` may give you `en`.
|
|
8
|
+
* @returns the locale that is now in use
|
|
9
|
+
*/
|
|
10
|
+
export async function loadLocale(language = globalThis.navigator?.language || currentLocale) {
|
|
11
|
+
const response = await fetch(new URL('/locales/' + encodeURIComponent(language), origin));
|
|
12
|
+
if (!response.ok) {
|
|
13
|
+
warn(`No translations for ${language}:`, response.headers.get('content-type') == 'application/json' ? (await response.json()).message : response.statusText);
|
|
14
|
+
return currentLocale;
|
|
15
|
+
}
|
|
16
|
+
const resolved = response.headers.get('Content-Language') || language;
|
|
17
|
+
extendLocale(resolved, await response.json());
|
|
18
|
+
useLocale(resolved);
|
|
19
|
+
debug('Using locale: ' + resolved);
|
|
20
|
+
return resolved;
|
|
21
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type InstallPhase = 'installing' | 'pending' | 'active' | 'failed' | 'none';
|
|
2
|
+
/** A message sent from a page to the service worker. */
|
|
3
|
+
export type PageMessage = {
|
|
4
|
+
type: 'activate';
|
|
5
|
+
} | {
|
|
6
|
+
type: 'status';
|
|
7
|
+
};
|
|
8
|
+
export interface WorkerInstalled {
|
|
9
|
+
type: 'install';
|
|
10
|
+
version: string;
|
|
11
|
+
base: string;
|
|
12
|
+
error: string | null;
|
|
13
|
+
}
|
|
14
|
+
export interface WorkerUpdateActivated {
|
|
15
|
+
type: 'update';
|
|
16
|
+
version: string;
|
|
17
|
+
base: string;
|
|
18
|
+
}
|
|
19
|
+
export type WorkerMessage = WorkerInstalled | WorkerUpdateActivated;
|
|
20
|
+
export interface Status {
|
|
21
|
+
/** Whether a worker is serving this page. False until the first registration activates. */
|
|
22
|
+
controlled: boolean;
|
|
23
|
+
/** A new build is downloading right now. */
|
|
24
|
+
downloading: boolean;
|
|
25
|
+
/** A new build has finished downloading and is waiting for {@link activate}. */
|
|
26
|
+
updateReady: boolean;
|
|
27
|
+
/** The last registration or precache failure. */
|
|
28
|
+
error: string | null;
|
|
29
|
+
readonly installPhase: InstallPhase;
|
|
30
|
+
}
|
|
31
|
+
export declare const supported: boolean;
|
|
32
|
+
export declare const state: Status;
|
|
33
|
+
export declare function subscribe(listener: (status: Status) => void): () => void;
|
|
34
|
+
/** Register the service worker. Idempotent. */
|
|
35
|
+
export declare function register(url?: string, options?: RegistrationOptions): Promise<ServiceWorkerRegistration | null>;
|
|
36
|
+
/** Remove the worker and everything it cached. Used when the `pwa` feature is turned off. */
|
|
37
|
+
export declare function unregister(): Promise<void>;
|
|
38
|
+
export declare function update(): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Apply a downloaded update.
|
|
41
|
+
* Tell the waiting worker to take over then reload.
|
|
42
|
+
* Does nothing when no update is pending.
|
|
43
|
+
*/
|
|
44
|
+
export declare function activate(): void;
|
package/dist/web/pwa.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { errorText, warn } from 'ioium';
|
|
2
|
+
export const supported = typeof navigator != 'undefined' && 'serviceWorker' in navigator;
|
|
3
|
+
export const state = {
|
|
4
|
+
controlled: supported && !!navigator.serviceWorker.controller,
|
|
5
|
+
downloading: false,
|
|
6
|
+
updateReady: false,
|
|
7
|
+
error: null,
|
|
8
|
+
get installPhase() {
|
|
9
|
+
if (state.downloading)
|
|
10
|
+
return 'installing';
|
|
11
|
+
if (state.updateReady)
|
|
12
|
+
return 'pending';
|
|
13
|
+
return state.controlled ? 'active' : 'none';
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
const listeners = new Set();
|
|
17
|
+
export function subscribe(listener) {
|
|
18
|
+
listeners.add(listener);
|
|
19
|
+
listener(state);
|
|
20
|
+
return () => listeners.delete(listener);
|
|
21
|
+
}
|
|
22
|
+
function change(changes) {
|
|
23
|
+
Object.assign(state, changes);
|
|
24
|
+
for (const listener of listeners)
|
|
25
|
+
listener(state);
|
|
26
|
+
}
|
|
27
|
+
let registration = null;
|
|
28
|
+
/** Set by {@link activate}, so only an update the user asked for reloads the page. */
|
|
29
|
+
let needsReload = false;
|
|
30
|
+
function track(worker) {
|
|
31
|
+
const check = () => {
|
|
32
|
+
switch (worker.state) {
|
|
33
|
+
case 'installing':
|
|
34
|
+
change({ downloading: true, updateReady: false });
|
|
35
|
+
break;
|
|
36
|
+
case 'installed':
|
|
37
|
+
change({ downloading: false, updateReady: !!navigator.serviceWorker.controller });
|
|
38
|
+
break;
|
|
39
|
+
case 'activated':
|
|
40
|
+
change({ downloading: false, updateReady: false, controlled: true });
|
|
41
|
+
break;
|
|
42
|
+
case 'redundant':
|
|
43
|
+
change({ downloading: false, updateReady: false });
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
worker.addEventListener('statechange', check);
|
|
48
|
+
check();
|
|
49
|
+
}
|
|
50
|
+
function formatVersion(timestamp) {
|
|
51
|
+
if (!/^\d+$/.test(timestamp))
|
|
52
|
+
return timestamp;
|
|
53
|
+
const pretty = new Date(Number(timestamp)).toISOString().split('.')[0];
|
|
54
|
+
return `${pretty} (${timestamp})`;
|
|
55
|
+
}
|
|
56
|
+
/** Register the service worker. Idempotent. */
|
|
57
|
+
export async function register(url = '/service-worker.js', options = {}) {
|
|
58
|
+
if (!supported)
|
|
59
|
+
return null;
|
|
60
|
+
console.debug('pwa: registering service worker');
|
|
61
|
+
navigator.serviceWorker.addEventListener('message', event => {
|
|
62
|
+
const message = event.data;
|
|
63
|
+
switch (message?.type) {
|
|
64
|
+
case 'install':
|
|
65
|
+
console.debug('pwa: installed', formatVersion(message.version));
|
|
66
|
+
change({ error: message.error ?? null });
|
|
67
|
+
break;
|
|
68
|
+
case 'update':
|
|
69
|
+
console.debug('pwa: updated service worker to', formatVersion(message.version));
|
|
70
|
+
break;
|
|
71
|
+
default:
|
|
72
|
+
warn('Unknown message from service worker:', message);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
|
76
|
+
change({ controlled: !!navigator.serviceWorker.controller, updateReady: false });
|
|
77
|
+
if (needsReload)
|
|
78
|
+
location.reload();
|
|
79
|
+
});
|
|
80
|
+
try {
|
|
81
|
+
registration = await navigator.serviceWorker.register(url, { ...options, type: 'module' });
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
console.error(e);
|
|
85
|
+
change({ error: errorText(e) });
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
if (registration.installing)
|
|
89
|
+
track(registration.installing);
|
|
90
|
+
if (registration.waiting)
|
|
91
|
+
change({ updateReady: true });
|
|
92
|
+
registration.addEventListener('updatefound', () => {
|
|
93
|
+
console.debug('pwa: updating');
|
|
94
|
+
if (registration?.installing)
|
|
95
|
+
track(registration.installing);
|
|
96
|
+
});
|
|
97
|
+
try {
|
|
98
|
+
const cacheKeys = await caches.keys();
|
|
99
|
+
const timestamps = cacheKeys.filter(k => k.startsWith('axium@')).map(k => k.slice('axium@'.length));
|
|
100
|
+
console.debug('pwa: service worker version(s):', timestamps.map(formatVersion).join(', '));
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
console.debug('pwa: could not get service worker versions,', errorText(e));
|
|
104
|
+
}
|
|
105
|
+
return registration;
|
|
106
|
+
}
|
|
107
|
+
/** Remove the worker and everything it cached. Used when the `pwa` feature is turned off. */
|
|
108
|
+
export async function unregister() {
|
|
109
|
+
if (!supported)
|
|
110
|
+
return;
|
|
111
|
+
registration ??= await navigator.serviceWorker.getRegistration();
|
|
112
|
+
await registration?.unregister();
|
|
113
|
+
registration = null;
|
|
114
|
+
for (const key of await caches.keys()) {
|
|
115
|
+
if (key.startsWith('axium@'))
|
|
116
|
+
await caches.delete(key);
|
|
117
|
+
}
|
|
118
|
+
change({ controlled: false, downloading: false, updateReady: false });
|
|
119
|
+
}
|
|
120
|
+
export async function update() {
|
|
121
|
+
if (!navigator.onLine)
|
|
122
|
+
console.debug('pwa: can not check for updates while offline');
|
|
123
|
+
console.debug('pwa: checking for updates');
|
|
124
|
+
await registration?.update();
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Apply a downloaded update.
|
|
128
|
+
* Tell the waiting worker to take over then reload.
|
|
129
|
+
* Does nothing when no update is pending.
|
|
130
|
+
*/
|
|
131
|
+
export function activate() {
|
|
132
|
+
const waiting = registration?.waiting;
|
|
133
|
+
if (!waiting)
|
|
134
|
+
return;
|
|
135
|
+
console.debug('pwa: activating service worker update');
|
|
136
|
+
needsReload = true;
|
|
137
|
+
waiting.postMessage({ type: 'activate' });
|
|
138
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-misused-promises */
|
|
2
|
+
import { base, build, files, version } from '$service-worker';
|
|
3
|
+
const cacheName = 'axium@' + version;
|
|
4
|
+
const versionFile = `${base}/_app/version.json`;
|
|
5
|
+
const precache = new Set([...build, ...files]);
|
|
6
|
+
async function install() {
|
|
7
|
+
let error = null;
|
|
8
|
+
try {
|
|
9
|
+
const cache = await caches.open(cacheName);
|
|
10
|
+
await Promise.all(Array.from(precache).map(url => cache.add(new Request(url, files.includes(url) ? { cache: 'no-cache' } : {}))));
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
error = e instanceof Error ? e.message : String(e);
|
|
14
|
+
await caches.delete(cacheName);
|
|
15
|
+
throw e;
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
const clients = await globalThis.clients.matchAll({ includeUncontrolled: true, type: 'window' });
|
|
19
|
+
for (const client of clients)
|
|
20
|
+
client.postMessage({ type: 'install', version, base, error });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
globalThis.addEventListener('install', event => event.waitUntil(install()));
|
|
24
|
+
async function activate() {
|
|
25
|
+
await Promise.all((await caches.keys()).filter(key => key.startsWith('axium@') && key !== cacheName).map(key => caches.delete(key)));
|
|
26
|
+
await globalThis.clients.claim();
|
|
27
|
+
const clients = await globalThis.clients.matchAll({ includeUncontrolled: true, type: 'window' });
|
|
28
|
+
for (const client of clients)
|
|
29
|
+
client.postMessage({ type: 'update', version, base });
|
|
30
|
+
}
|
|
31
|
+
globalThis.addEventListener('activate', event => event.waitUntil(activate()));
|
|
32
|
+
globalThis.addEventListener('fetch', async (event) => {
|
|
33
|
+
const { request } = event;
|
|
34
|
+
if (request.method != 'GET')
|
|
35
|
+
return;
|
|
36
|
+
const url = new URL(request.url);
|
|
37
|
+
if (url.origin != location.origin)
|
|
38
|
+
return;
|
|
39
|
+
if (url.pathname == versionFile)
|
|
40
|
+
return;
|
|
41
|
+
// @todo API responses, `/raw`.
|
|
42
|
+
if (!precache.has(url.pathname))
|
|
43
|
+
return;
|
|
44
|
+
const cache = await caches.open(cacheName);
|
|
45
|
+
const hit = await cache.match(url.pathname);
|
|
46
|
+
if (hit)
|
|
47
|
+
return event.respondWith(hit);
|
|
48
|
+
const response = await fetch(request);
|
|
49
|
+
if (response.ok && response.status != 206)
|
|
50
|
+
await cache.put(url.pathname, response.clone());
|
|
51
|
+
return event.respondWith(response);
|
|
52
|
+
});
|
|
53
|
+
globalThis.addEventListener('message', (event) => {
|
|
54
|
+
if (!event.data)
|
|
55
|
+
console.warn('Ignoring invalid message (no data)');
|
|
56
|
+
switch (event.data?.type) {
|
|
57
|
+
case 'status':
|
|
58
|
+
event.source?.postMessage(status);
|
|
59
|
+
break;
|
|
60
|
+
case 'activate':
|
|
61
|
+
console.log('activating update');
|
|
62
|
+
void globalThis.skipWaiting();
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
console.log('version is', version);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function hasActiveInput(): boolean;
|
package/lib/ClipboardCopy.svelte
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { fade } from 'svelte/transition';
|
|
3
3
|
import { wait } from 'utilium';
|
|
4
4
|
import Icon from './Icon.svelte';
|
|
5
|
-
import * as clip from '@axium/client/
|
|
5
|
+
import * as clip from '@axium/client/web';
|
|
6
6
|
|
|
7
7
|
const { value, type = 'text/plain' }: { value: BlobPart; type?: string } = $props();
|
|
8
8
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { pwa } from '@axium/client/web';
|
|
3
|
+
import feature from '@axium/core/features';
|
|
4
|
+
import Icon from './Icon.svelte';
|
|
5
|
+
|
|
6
|
+
const iconMap = {
|
|
7
|
+
installing: 'regular/cloud-arrow-down',
|
|
8
|
+
pending: 'circle-arrow-up',
|
|
9
|
+
active: 'regular/cloud-check',
|
|
10
|
+
failed: 'cloud-xmark',
|
|
11
|
+
none: 'regular/cloud-slash',
|
|
12
|
+
} satisfies Record<pwa.InstallPhase, string>;
|
|
13
|
+
|
|
14
|
+
function onclick() {
|
|
15
|
+
if ($pwa.updateReady) pwa.activate();
|
|
16
|
+
else pwa.update();
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
{#if pwa.supported && feature('pwa')}
|
|
21
|
+
<Icon class="PWAIndicator" i={iconMap[$pwa.installPhase]} {onclick} />
|
|
22
|
+
{/if}
|
package/lib/ZodForm.svelte
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { type ReplacementOptions, text } from '@axium/client/locales';
|
|
3
|
+
import { zKeys } from '@axium/core/locales';
|
|
2
4
|
import type { ZodObject } from 'zod';
|
|
3
5
|
import ZodInput from './ZodInput.svelte';
|
|
4
6
|
|
|
@@ -11,11 +13,18 @@
|
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
let { rootValue = $bindable(), schema, labels, updateValue, idPrefix }: Props = $props();
|
|
16
|
+
|
|
17
|
+
const localeInfo = zKeys.get(schema);
|
|
18
|
+
|
|
19
|
+
function subText(name: string, replacements?: ReplacementOptions & Record<string, any>): string | undefined {
|
|
20
|
+
if (!localeInfo?.prefix) return replacements?.$default || labels?.[name] || name;
|
|
21
|
+
return text(`${localeInfo.prefix}.${name}`, replacements);
|
|
22
|
+
}
|
|
14
23
|
</script>
|
|
15
24
|
|
|
16
25
|
<div class="ZodForm">
|
|
17
26
|
{#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={
|
|
27
|
+
<ZodInput bind:rootValue {updateValue} {idPrefix} {path} schema={schema.shape[path]} label={subText(path)} />
|
|
19
28
|
{/each}
|
|
20
29
|
</div>
|
|
21
30
|
|
package/lib/ZodInput.svelte
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { text, type ReplacementOptions } from '@axium/client';
|
|
3
3
|
import type { ZodPref } from '@axium/core';
|
|
4
|
+
import feature from '@axium/core/features';
|
|
4
5
|
import { zKeys, type ZodLocaleInfo } from '@axium/core/locales';
|
|
5
6
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
6
7
|
import { getByString, pick, setByString } from 'utilium';
|
|
@@ -138,7 +139,9 @@
|
|
|
138
139
|
</div>
|
|
139
140
|
{/snippet}
|
|
140
141
|
|
|
141
|
-
{#if
|
|
142
|
+
{#if localeInfo?.feature && !feature(localeInfo.feature)}
|
|
143
|
+
<meta data-feature={localeInfo.feature} />
|
|
144
|
+
{:else if schema.type == 'string'}
|
|
142
145
|
{@render _in({ type: schema.format == 'email' ? 'email' : 'text', ...pick(schema, 'minLength', 'maxLength') })}
|
|
143
146
|
{:else if schema.type == 'number'}
|
|
144
147
|
{@render _in({ type: 'number', min: schema.minValue, max: schema.maxValue, step: schema.format?.includes('int') ? 1 : 0.1 })}
|
package/lib/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ export { default as Logout } from './Logout.svelte';
|
|
|
16
16
|
export { default as MediaControls } from './MediaControls.svelte';
|
|
17
17
|
export { default as NumberBar } from './NumberBar.svelte';
|
|
18
18
|
export { default as Popover } from './Popover.svelte';
|
|
19
|
+
export { default as PWAIndicator } from './PWAIndicator.svelte';
|
|
19
20
|
export { default as Register } from './Register.svelte';
|
|
20
21
|
export { default as SessionList } from './SessionList.svelte';
|
|
21
22
|
export { default as SidebarLayout } from './SidebarLayout.svelte';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { SyncDiff, SyncDiffObject } from '@axium/core';
|
|
2
|
+
import { hasActiveInput } from '@axium/client/web/utils';
|
|
3
|
+
import { applyDiff, onSync } from '@axium/client/sync';
|
|
4
|
+
|
|
5
|
+
export function syncObjects<T extends { id: string }>(objects: T[], type?: string) {
|
|
6
|
+
let deferred: SyncDiffObject[] = [];
|
|
7
|
+
|
|
8
|
+
function applySync(diff: SyncDiff): void {
|
|
9
|
+
const updated = [...deferred, ...diff.updated];
|
|
10
|
+
const typing = hasActiveInput();
|
|
11
|
+
|
|
12
|
+
deferred = typing ? updated : [];
|
|
13
|
+
|
|
14
|
+
applyDiff(objects, { ...diff, updated: typing ? [] : updated }, type);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
$effect(() => onSync(applySync));
|
|
18
|
+
|
|
19
|
+
return function onfocusout() {
|
|
20
|
+
// Focus moves after this event, so wait for it to settle before checking whether the user is still typing
|
|
21
|
+
setTimeout(() => {
|
|
22
|
+
if (deferred.length && !hasActiveInput()) applySync({ created: [], updated: [], deleted: [], index: 0n });
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function syncObject<T extends { id: string }>(object: T, onDelete?: () => void, type?: string) {
|
|
28
|
+
let deferred: SyncDiffObject[] = [];
|
|
29
|
+
|
|
30
|
+
function applySync(diff: SyncDiff): void {
|
|
31
|
+
if (diff.deleted.includes(object.id)) {
|
|
32
|
+
onDelete?.();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const updated = [...deferred, ...diff.updated];
|
|
37
|
+
const typing = hasActiveInput();
|
|
38
|
+
|
|
39
|
+
deferred = typing ? updated : [];
|
|
40
|
+
|
|
41
|
+
applyDiff([object], { ...diff, created: [], updated: typing ? [] : updated }, type);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
$effect(() => onSync(applySync));
|
|
45
|
+
}
|
package/lib/toast.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { debug, errorText, useProgress } from 'ioium';
|
|
2
2
|
import { text } from '@axium/client/locales';
|
|
3
|
-
import { animate, onAnimationEnd } from '@axium/client/
|
|
3
|
+
import { animate, onAnimationEnd } from '@axium/client/web';
|
|
4
4
|
import Icon from './Icon.svelte';
|
|
5
5
|
import { mount } from 'svelte';
|
|
6
6
|
import { $ZodError, prettifyError } from 'zod/v4/core';
|
package/lib/tsconfig.json
CHANGED
package/locales/en.json
CHANGED
|
@@ -249,6 +249,16 @@
|
|
|
249
249
|
},
|
|
250
250
|
"audit_severity": ["Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Info", "Debug"],
|
|
251
251
|
"preference": {
|
|
252
|
-
"debug": "Debug mode"
|
|
252
|
+
"debug": "Debug mode",
|
|
253
|
+
"theme": "Theme"
|
|
254
|
+
},
|
|
255
|
+
"themes": {
|
|
256
|
+
"default": "Default",
|
|
257
|
+
"light": "Light",
|
|
258
|
+
"forest": "Forest",
|
|
259
|
+
"midnight": "Midnight",
|
|
260
|
+
"beach": "Beach",
|
|
261
|
+
"cherry": "Cherry",
|
|
262
|
+
"volcano": "Volcano"
|
|
253
263
|
}
|
|
254
264
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axium/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"author": "James Prevett <jp@jamespre.dev>",
|
|
5
5
|
"funding": {
|
|
6
6
|
"type": "individual",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"exports": {
|
|
34
34
|
"./package.json": "./package.json",
|
|
35
35
|
".": "./dist/index.js",
|
|
36
|
-
"./
|
|
36
|
+
"./web": "./dist/web/index.js",
|
|
37
37
|
"./*": "./dist/*.js",
|
|
38
38
|
"./components": "./lib/index.js",
|
|
39
39
|
"./components/*": "./lib/*.svelte",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"build": "tsc"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@axium/core": ">=0.
|
|
52
|
+
"@axium/core": ">=0.39.0",
|
|
53
53
|
"ioium": "^1.7.0",
|
|
54
54
|
"semver": "^7.7.4",
|
|
55
55
|
"svelte": "^5.36.0",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|