@rokkit/app 1.3.4 → 1.3.6
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/{src/components/index.ts → dist/components/index.js} +3 -3
- package/dist/index.js +6 -0
- package/dist/types/index.js +2 -0
- package/dist/types/skin-switcher.js +8 -0
- package/dist/types/theme-switcher.js +24 -0
- package/dist/utils/color-mode.svelte.js +80 -0
- package/package.json +12 -13
- package/src/index.ts +0 -13
- package/src/types/index.ts +0 -2
- package/src/types/skin-switcher.ts +0 -20
- package/src/types/theme-switcher.ts +0 -116
- package/src/utils/color-mode.svelte.ts +0 -96
- /package/{src → dist}/components/SkinSwitcherToggle.svelte +0 -0
- /package/{src → dist}/components/TableOfContents.svelte +0 -0
- /package/{src → dist}/components/ThemeSwitcherToggle.svelte +0 -0
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { default as ThemeSwitcherToggle } from './ThemeSwitcherToggle.svelte'
|
|
2
|
-
export { default as TableOfContents } from './TableOfContents.svelte'
|
|
3
|
-
export { default as SkinSwitcherToggle } from './SkinSwitcherToggle.svelte'
|
|
1
|
+
export { default as ThemeSwitcherToggle } from './ThemeSwitcherToggle.svelte';
|
|
2
|
+
export { default as TableOfContents } from './TableOfContents.svelte';
|
|
3
|
+
export { default as SkinSwitcherToggle } from './SkinSwitcherToggle.svelte';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function buildSkinSwitcherOptions(skins, labels = {}) {
|
|
2
|
+
return skins.map((s) => {
|
|
3
|
+
const name = typeof s === 'string' ? s : s.name;
|
|
4
|
+
const label = typeof s === 'string' ? (labels[s] ?? s) : (s.label ?? labels[s.name] ?? s.name);
|
|
5
|
+
const icon = typeof s === 'string' ? undefined : s.icon;
|
|
6
|
+
return { value: name, label, icon };
|
|
7
|
+
});
|
|
8
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ThemeSwitcherToggle Types
|
|
3
|
+
*
|
|
4
|
+
* Types and defaults for the theme mode switcher component.
|
|
5
|
+
*/
|
|
6
|
+
import { DEFAULT_STATE_ICONS } from '@rokkit/core';
|
|
7
|
+
import { messages } from '@rokkit/states';
|
|
8
|
+
export const defaultThemeSwitcherIcons = {
|
|
9
|
+
system: DEFAULT_STATE_ICONS.mode.system,
|
|
10
|
+
light: DEFAULT_STATE_ICONS.mode.light,
|
|
11
|
+
dark: DEFAULT_STATE_ICONS.mode.dark
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Build toggle options from icons, modes, and labels
|
|
15
|
+
*/
|
|
16
|
+
export function buildThemeSwitcherOptions(icons, modes, labels = {}) {
|
|
17
|
+
const mergedLabels = { ...messages.mode, ...labels };
|
|
18
|
+
const all = [
|
|
19
|
+
{ value: 'system', label: mergedLabels.system, icon: icons.system },
|
|
20
|
+
{ value: 'light', label: mergedLabels.light, icon: icons.light },
|
|
21
|
+
{ value: 'dark', label: mergedLabels.dark, icon: icons.dark }
|
|
22
|
+
];
|
|
23
|
+
return all.filter((o) => modes.includes(o.value));
|
|
24
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color Mode Manager
|
|
3
|
+
*
|
|
4
|
+
* Bridges three-mode UI (system/light/dark) with vibe's two-mode store (light/dark).
|
|
5
|
+
* Resolves 'system' to actual OS preference via matchMedia.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Resolve a ColorMode to an actual light/dark value.
|
|
9
|
+
* When 'system', queries the OS preference via matchMedia.
|
|
10
|
+
*/
|
|
11
|
+
export function resolveMode(mode) {
|
|
12
|
+
if (mode === 'system') {
|
|
13
|
+
if (typeof window !== 'undefined') {
|
|
14
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
15
|
+
}
|
|
16
|
+
return 'dark'; // SSR: no window
|
|
17
|
+
}
|
|
18
|
+
return mode;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Reactive color mode manager that tracks system/light/dark,
|
|
22
|
+
* resolves to actual light/dark, and syncs to a theme target (e.g. vibe).
|
|
23
|
+
*/
|
|
24
|
+
export class ColorModeManager {
|
|
25
|
+
#mode = $state('system');
|
|
26
|
+
#resolved = $state('dark');
|
|
27
|
+
#target;
|
|
28
|
+
constructor(target, initialMode = 'system') {
|
|
29
|
+
this.#target = target;
|
|
30
|
+
this.#mode = initialMode;
|
|
31
|
+
// Don't override target.mode at construction. If the target already
|
|
32
|
+
// has a meaningful light/dark value (loaded from storage, set by the
|
|
33
|
+
// flash-prevention init script, or persisted by a prior session),
|
|
34
|
+
// adopt it as the resolved state. Only fall back to deriving from
|
|
35
|
+
// `initialMode` when the target has no value to read.
|
|
36
|
+
//
|
|
37
|
+
// Without this guard, every mount of a consumer like
|
|
38
|
+
// `<ThemeSwitcherToggle/>` flipped target.mode to OS preference,
|
|
39
|
+
// causing user-visible mode changes on navigation between routes.
|
|
40
|
+
if (target.mode === 'light' || target.mode === 'dark') {
|
|
41
|
+
this.#resolved = target.mode;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
this.#resolved = resolveMode(initialMode);
|
|
45
|
+
this.#target.mode = this.#resolved;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
get mode() {
|
|
49
|
+
return this.#mode;
|
|
50
|
+
}
|
|
51
|
+
set mode(value) {
|
|
52
|
+
if (value === this.#mode)
|
|
53
|
+
return;
|
|
54
|
+
this.#mode = value;
|
|
55
|
+
this.#resolved = resolveMode(value);
|
|
56
|
+
this.#target.mode = this.#resolved;
|
|
57
|
+
}
|
|
58
|
+
get resolved() {
|
|
59
|
+
return this.#resolved;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Start listening for OS preference changes.
|
|
63
|
+
* Returns a cleanup function to stop listening.
|
|
64
|
+
* Call in onMount or $effect.root.
|
|
65
|
+
*/
|
|
66
|
+
listen() {
|
|
67
|
+
/* v8 ignore next -- SSR path: window is undefined in Node/server environments */
|
|
68
|
+
if (typeof window === 'undefined')
|
|
69
|
+
return () => { };
|
|
70
|
+
const mq = window.matchMedia('(prefers-color-scheme: dark)');
|
|
71
|
+
const handler = () => {
|
|
72
|
+
if (this.#mode === 'system') {
|
|
73
|
+
this.#resolved = resolveMode('system');
|
|
74
|
+
this.#target.mode = this.#resolved;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
mq.addEventListener('change', handler);
|
|
78
|
+
return () => mq.removeEventListener('change', handler);
|
|
79
|
+
}
|
|
80
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rokkit/app",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.6",
|
|
4
4
|
"description": "App-level controls for Rokkit applications - theme management and UI chrome",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -10,38 +10,37 @@
|
|
|
10
10
|
"publishConfig": {
|
|
11
11
|
"access": "public"
|
|
12
12
|
},
|
|
13
|
-
"svelte": "./
|
|
13
|
+
"svelte": "./dist/index.js",
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
15
|
"exports": {
|
|
16
16
|
".": {
|
|
17
17
|
"types": "./dist/index.d.ts",
|
|
18
|
-
"svelte": "./
|
|
19
|
-
"default": "./
|
|
18
|
+
"svelte": "./dist/index.js",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
20
|
},
|
|
21
21
|
"./types": {
|
|
22
22
|
"types": "./dist/types/index.d.ts",
|
|
23
|
-
"default": "./
|
|
23
|
+
"default": "./dist/types/index.js"
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
|
-
"
|
|
28
|
-
"dist/**/*.d.ts",
|
|
27
|
+
"dist",
|
|
29
28
|
"README.md",
|
|
30
29
|
"LICENSE"
|
|
31
30
|
],
|
|
32
31
|
"scripts": {
|
|
33
|
-
"prepublishOnly": "cp ../../LICENSE . && bun run build",
|
|
34
|
-
"postpublish": "rm -f LICENSE",
|
|
32
|
+
"prepublishOnly": "cp ../../LICENSE . && bun run build && node ../../config/pack-repoint.mjs",
|
|
33
|
+
"postpublish": "node ../../config/pack-repoint.mjs --restore && rm -f LICENSE",
|
|
35
34
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
36
35
|
"test": "vitest run",
|
|
37
36
|
"test:watch": "vitest",
|
|
38
37
|
"build": "svelte-package -i src -o dist"
|
|
39
38
|
},
|
|
40
39
|
"dependencies": {
|
|
41
|
-
"@rokkit/actions": "1.3.
|
|
42
|
-
"@rokkit/core": "1.3.
|
|
43
|
-
"@rokkit/states": "1.3.
|
|
44
|
-
"@rokkit/ui": "1.3.
|
|
40
|
+
"@rokkit/actions": "1.3.6",
|
|
41
|
+
"@rokkit/core": "1.3.6",
|
|
42
|
+
"@rokkit/states": "1.3.6",
|
|
43
|
+
"@rokkit/ui": "1.3.6"
|
|
45
44
|
},
|
|
46
45
|
"peerDependencies": {
|
|
47
46
|
"svelte": "^5.0.0"
|
package/src/index.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// Components
|
|
2
|
-
export { ThemeSwitcherToggle, SkinSwitcherToggle, TableOfContents } from './components/index.js'
|
|
3
|
-
|
|
4
|
-
// Types
|
|
5
|
-
export * from './types/index.js'
|
|
6
|
-
|
|
7
|
-
// Utilities
|
|
8
|
-
export {
|
|
9
|
-
ColorModeManager,
|
|
10
|
-
resolveMode,
|
|
11
|
-
type ColorMode,
|
|
12
|
-
type ResolvedMode
|
|
13
|
-
} from './utils/color-mode.svelte.js'
|
package/src/types/index.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
// A `type` alias (not an `interface`) so it satisfies the
|
|
2
|
-
// `Record<string, unknown>` index signature that Toggle's `ToggleItem`
|
|
3
|
-
// expects — TS interfaces don't implicitly carry an index signature.
|
|
4
|
-
export type SkinSwitcherOption = {
|
|
5
|
-
value: string
|
|
6
|
-
label: string
|
|
7
|
-
icon?: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function buildSkinSwitcherOptions(
|
|
11
|
-
skins: Array<string | { name: string; label?: string; icon?: string }>,
|
|
12
|
-
labels: Record<string, string> = {}
|
|
13
|
-
): SkinSwitcherOption[] {
|
|
14
|
-
return skins.map((s) => {
|
|
15
|
-
const name = typeof s === 'string' ? s : s.name
|
|
16
|
-
const label = typeof s === 'string' ? (labels[s] ?? s) : (s.label ?? labels[s.name] ?? s.name)
|
|
17
|
-
const icon = typeof s === 'string' ? undefined : s.icon
|
|
18
|
-
return { value: name, label, icon }
|
|
19
|
-
})
|
|
20
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ThemeSwitcherToggle Types
|
|
3
|
-
*
|
|
4
|
-
* Types and defaults for the theme mode switcher component.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { ToggleItemSnippet } from '@rokkit/ui'
|
|
8
|
-
import { DEFAULT_STATE_ICONS } from '@rokkit/core'
|
|
9
|
-
import { messages } from '@rokkit/states'
|
|
10
|
-
import type { ColorMode } from '../utils/color-mode.svelte.js'
|
|
11
|
-
|
|
12
|
-
// =============================================================================
|
|
13
|
-
// Icons
|
|
14
|
-
// =============================================================================
|
|
15
|
-
|
|
16
|
-
export interface ThemeSwitcherIcons {
|
|
17
|
-
system?: string
|
|
18
|
-
light?: string
|
|
19
|
-
dark?: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const defaultThemeSwitcherIcons: Required<ThemeSwitcherIcons> = {
|
|
23
|
-
system: DEFAULT_STATE_ICONS.mode.system,
|
|
24
|
-
light: DEFAULT_STATE_ICONS.mode.light,
|
|
25
|
-
dark: DEFAULT_STATE_ICONS.mode.dark
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// =============================================================================
|
|
29
|
-
// Options
|
|
30
|
-
// =============================================================================
|
|
31
|
-
|
|
32
|
-
export interface ThemeSwitcherOption {
|
|
33
|
-
value: ColorMode
|
|
34
|
-
label: string
|
|
35
|
-
icon: string
|
|
36
|
-
[key: string]: unknown
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface ThemeSwitcherLabels {
|
|
40
|
-
system?: string
|
|
41
|
-
light?: string
|
|
42
|
-
dark?: string
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Build toggle options from icons, modes, and labels
|
|
47
|
-
*/
|
|
48
|
-
export function buildThemeSwitcherOptions(
|
|
49
|
-
icons: Required<ThemeSwitcherIcons>,
|
|
50
|
-
modes: ColorMode[],
|
|
51
|
-
labels: ThemeSwitcherLabels = {}
|
|
52
|
-
): ThemeSwitcherOption[] {
|
|
53
|
-
const mergedLabels = { ...messages.mode, ...labels }
|
|
54
|
-
const all: ThemeSwitcherOption[] = [
|
|
55
|
-
{ value: 'system', label: mergedLabels.system, icon: icons.system },
|
|
56
|
-
{ value: 'light', label: mergedLabels.light, icon: icons.light },
|
|
57
|
-
{ value: 'dark', label: mergedLabels.dark, icon: icons.dark }
|
|
58
|
-
]
|
|
59
|
-
return all.filter((o) => modes.includes(o.value))
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// =============================================================================
|
|
63
|
-
// Component Props
|
|
64
|
-
// =============================================================================
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Visual variant.
|
|
68
|
-
* - `'single'` — one icon-only button that cycles between light and dark on click.
|
|
69
|
-
* System mode is not part of the cycle. The icon shows the mode you'd switch *to*.
|
|
70
|
-
* - `'pair'` — two buttons: light and dark. Equivalent to `includeSystem={false}`
|
|
71
|
-
* for the legacy triad UI.
|
|
72
|
-
* - `'triad'` — three buttons: system, light, dark. Default.
|
|
73
|
-
*/
|
|
74
|
-
export type ThemeSwitcherVariant = 'single' | 'pair' | 'triad'
|
|
75
|
-
|
|
76
|
-
export interface ThemeSwitcherToggleProps {
|
|
77
|
-
/** Visual variant. Default: 'triad'. */
|
|
78
|
-
variant?: ThemeSwitcherVariant
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Which modes to show. Only honored for `variant: 'triad'`.
|
|
82
|
-
* Default: `['system', 'light', 'dark']`.
|
|
83
|
-
*/
|
|
84
|
-
modes?: ColorMode[]
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Whether to include system mode. Default: true. Shortcut for filtering 'system'
|
|
88
|
-
* from modes. Only honored for `variant: 'triad'`. For `'pair'` and `'single'`
|
|
89
|
-
* system is never included.
|
|
90
|
-
*/
|
|
91
|
-
includeSystem?: boolean
|
|
92
|
-
|
|
93
|
-
/** Override icons per mode */
|
|
94
|
-
icons?: ThemeSwitcherIcons
|
|
95
|
-
|
|
96
|
-
/** Override labels per mode. Merged over messages.mode */
|
|
97
|
-
labels?: ThemeSwitcherLabels
|
|
98
|
-
|
|
99
|
-
/** Show text labels alongside icons. Ignored for `variant: 'single'`. Default: false */
|
|
100
|
-
showLabels?: boolean
|
|
101
|
-
|
|
102
|
-
/** Size variant. Default: 'sm' */
|
|
103
|
-
size?: 'sm' | 'md' | 'lg'
|
|
104
|
-
|
|
105
|
-
/** Disabled state */
|
|
106
|
-
disabled?: boolean
|
|
107
|
-
|
|
108
|
-
/** Additional CSS classes */
|
|
109
|
-
class?: string
|
|
110
|
-
|
|
111
|
-
/** Custom snippet for rendering toggle items (only honored for `'pair'` / `'triad'`) */
|
|
112
|
-
item?: ToggleItemSnippet
|
|
113
|
-
|
|
114
|
-
/** Called when mode changes */
|
|
115
|
-
onchange?: (mode: ColorMode) => void
|
|
116
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Color Mode Manager
|
|
3
|
-
*
|
|
4
|
-
* Bridges three-mode UI (system/light/dark) with vibe's two-mode store (light/dark).
|
|
5
|
-
* Resolves 'system' to actual OS preference via matchMedia.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export type ColorMode = 'system' | 'light' | 'dark'
|
|
9
|
-
export type ResolvedMode = 'light' | 'dark'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Resolve a ColorMode to an actual light/dark value.
|
|
13
|
-
* When 'system', queries the OS preference via matchMedia.
|
|
14
|
-
*/
|
|
15
|
-
export function resolveMode(mode: ColorMode): ResolvedMode {
|
|
16
|
-
if (mode === 'system') {
|
|
17
|
-
if (typeof window !== 'undefined') {
|
|
18
|
-
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
19
|
-
}
|
|
20
|
-
return 'dark' // SSR: no window
|
|
21
|
-
}
|
|
22
|
-
return mode
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Theme target interface — anything with a settable mode property.
|
|
27
|
-
* Compatible with the vibe singleton from @rokkit/states.
|
|
28
|
-
*/
|
|
29
|
-
interface ThemeTarget {
|
|
30
|
-
mode: string
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Reactive color mode manager that tracks system/light/dark,
|
|
35
|
-
* resolves to actual light/dark, and syncs to a theme target (e.g. vibe).
|
|
36
|
-
*/
|
|
37
|
-
export class ColorModeManager {
|
|
38
|
-
#mode = $state<ColorMode>('system')
|
|
39
|
-
#resolved = $state<ResolvedMode>('dark')
|
|
40
|
-
#target: ThemeTarget
|
|
41
|
-
|
|
42
|
-
constructor(target: ThemeTarget, initialMode: ColorMode = 'system') {
|
|
43
|
-
this.#target = target
|
|
44
|
-
this.#mode = initialMode
|
|
45
|
-
// Don't override target.mode at construction. If the target already
|
|
46
|
-
// has a meaningful light/dark value (loaded from storage, set by the
|
|
47
|
-
// flash-prevention init script, or persisted by a prior session),
|
|
48
|
-
// adopt it as the resolved state. Only fall back to deriving from
|
|
49
|
-
// `initialMode` when the target has no value to read.
|
|
50
|
-
//
|
|
51
|
-
// Without this guard, every mount of a consumer like
|
|
52
|
-
// `<ThemeSwitcherToggle/>` flipped target.mode to OS preference,
|
|
53
|
-
// causing user-visible mode changes on navigation between routes.
|
|
54
|
-
if (target.mode === 'light' || target.mode === 'dark') {
|
|
55
|
-
this.#resolved = target.mode
|
|
56
|
-
} else {
|
|
57
|
-
this.#resolved = resolveMode(initialMode)
|
|
58
|
-
this.#target.mode = this.#resolved
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
get mode(): ColorMode {
|
|
63
|
-
return this.#mode
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
set mode(value: ColorMode) {
|
|
67
|
-
if (value === this.#mode) return
|
|
68
|
-
this.#mode = value
|
|
69
|
-
this.#resolved = resolveMode(value)
|
|
70
|
-
this.#target.mode = this.#resolved
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
get resolved(): ResolvedMode {
|
|
74
|
-
return this.#resolved
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Start listening for OS preference changes.
|
|
79
|
-
* Returns a cleanup function to stop listening.
|
|
80
|
-
* Call in onMount or $effect.root.
|
|
81
|
-
*/
|
|
82
|
-
listen(): () => void {
|
|
83
|
-
/* v8 ignore next -- SSR path: window is undefined in Node/server environments */
|
|
84
|
-
if (typeof window === 'undefined') return () => {}
|
|
85
|
-
|
|
86
|
-
const mq = window.matchMedia('(prefers-color-scheme: dark)')
|
|
87
|
-
const handler = () => {
|
|
88
|
-
if (this.#mode === 'system') {
|
|
89
|
-
this.#resolved = resolveMode('system')
|
|
90
|
-
this.#target.mode = this.#resolved
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
mq.addEventListener('change', handler)
|
|
94
|
-
return () => mq.removeEventListener('change', handler)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|