@bunnyland/ui-web 0.2.0 → 0.2.2
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/README.md +4 -3
- package/assets/bunnyland-api.js +10 -6
- package/assets/bunnyland-ui.css +122 -3
- package/assets/bunnyland-ui.js +171 -14
- package/dist/theme.d.ts +1 -1
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +10 -7
- package/dist/theme.js.map +1 -1
- package/package.json +1 -1
- package/src/theme.ts +7 -4
package/README.md
CHANGED
|
@@ -99,7 +99,7 @@ source directory or a checked-in tarball.
|
|
|
99
99
|
The package owns the shared `--bl-*` CSS variables and theme selector helpers. Built-in
|
|
100
100
|
palette names are:
|
|
101
101
|
|
|
102
|
-
- `
|
|
102
|
+
- `midnight`
|
|
103
103
|
- `candy`
|
|
104
104
|
- `earth`
|
|
105
105
|
- `ocean`
|
|
@@ -110,8 +110,9 @@ Each palette follows `prefers-color-scheme` by default. The shared client menu c
|
|
|
110
110
|
Dark or Light appearance, or return to Auto (System). Use `bindThemeSelect(select)` for
|
|
111
111
|
palette selectors, `bindColorSchemeSelect(select)` for appearance selectors, and
|
|
112
112
|
`setTheme(theme)` or `setColorScheme(scheme)` for direct changes. Old paired values such as
|
|
113
|
-
`anime-light` continue to load as migration aliases (`candy` with Light forced)
|
|
114
|
-
should be added as CSS variables before
|
|
113
|
+
`anime-light` continue to load as migration aliases (`candy` with Light forced), as do the
|
|
114
|
+
former `purple-blue` values (`midnight`). New colors should be added as CSS variables before
|
|
115
|
+
clients depend on them.
|
|
115
116
|
|
|
116
117
|
Deployments can add their own theme choices without changing this package:
|
|
117
118
|
|
package/assets/bunnyland-api.js
CHANGED
|
@@ -196,15 +196,19 @@
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
async function promptPlayerAuth(base) {
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
199
|
+
const credentials = await window.BunnylandUI.credentialsDialog({
|
|
200
|
+
message: 'Enter your player credentials to continue.',
|
|
201
|
+
title: 'Sign in to Bunnyland',
|
|
202
|
+
});
|
|
203
|
+
if (!credentials) return null;
|
|
203
204
|
try {
|
|
204
|
-
await login(base, username, password);
|
|
205
|
+
await login(base, credentials.username, credentials.password);
|
|
205
206
|
return true;
|
|
206
207
|
} catch (error) {
|
|
207
|
-
window.
|
|
208
|
+
await window.BunnylandUI.alertDialog(error.message || 'Login failed', {
|
|
209
|
+
title: 'Sign in failed',
|
|
210
|
+
tone: 'danger',
|
|
211
|
+
});
|
|
208
212
|
return false;
|
|
209
213
|
}
|
|
210
214
|
}
|
package/assets/bunnyland-ui.css
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
:root,
|
|
2
2
|
:root.bl-theme-dark,
|
|
3
3
|
:root.bl-theme-purple-blue-dark,
|
|
4
|
-
:root.bl-theme-
|
|
4
|
+
:root.bl-theme-midnight-dark,
|
|
5
|
+
:root.bl-theme-purple-blue,
|
|
6
|
+
:root.bl-theme-midnight {
|
|
5
7
|
color-scheme: dark;
|
|
6
8
|
|
|
7
9
|
--bl-font-mono: 'Courier New', monospace;
|
|
8
10
|
--bl-radius-sm: 3px;
|
|
9
11
|
--bl-radius-md: 4px;
|
|
12
|
+
--bl-radius-lg: 6px;
|
|
10
13
|
--bl-text-xs: 10px;
|
|
11
14
|
--bl-text-sm: 11px;
|
|
12
15
|
--bl-text-md: 12px;
|
|
16
|
+
--bl-text-xl: 18px;
|
|
13
17
|
|
|
14
18
|
--bl-bg: #1e1e2e;
|
|
15
19
|
--bl-bg-strong: #181825;
|
|
@@ -34,6 +38,8 @@
|
|
|
34
38
|
--bl-ok: #a6e3a1;
|
|
35
39
|
--bl-error: #f38ba8;
|
|
36
40
|
--bl-warn: #fab387;
|
|
41
|
+
--bl-danger: var(--bl-error);
|
|
42
|
+
--bl-warning: var(--bl-warn);
|
|
37
43
|
--bl-info: #74c7ec;
|
|
38
44
|
|
|
39
45
|
--bl-shadow-popover: 0 4px 12px rgba(0, 0, 0, 0.5);
|
|
@@ -50,7 +56,9 @@
|
|
|
50
56
|
:root,
|
|
51
57
|
:root.bl-theme-light,
|
|
52
58
|
:root.bl-theme-purple-blue-light,
|
|
53
|
-
:root.bl-theme-
|
|
59
|
+
:root.bl-theme-midnight-light,
|
|
60
|
+
:root.bl-theme-purple-blue,
|
|
61
|
+
:root.bl-theme-midnight {
|
|
54
62
|
|
|
55
63
|
--bl-light-bg: #f4f1ea;
|
|
56
64
|
--bl-light-bg-strong: #ebe6dc;
|
|
@@ -487,6 +495,7 @@
|
|
|
487
495
|
|
|
488
496
|
@media (prefers-color-scheme: light) {
|
|
489
497
|
:root:not([class*="bl-theme-"]):not(.bl-color-scheme-dark),
|
|
498
|
+
:root.bl-theme-midnight:not(.bl-color-scheme-dark),
|
|
490
499
|
:root.bl-theme-purple-blue:not(.bl-color-scheme-dark),
|
|
491
500
|
:root.bl-theme-candy:not(.bl-color-scheme-dark),
|
|
492
501
|
:root.bl-theme-earth:not(.bl-color-scheme-dark),
|
|
@@ -530,6 +539,7 @@
|
|
|
530
539
|
}
|
|
531
540
|
}
|
|
532
541
|
|
|
542
|
+
:root.bl-theme-midnight.bl-color-scheme-light,
|
|
533
543
|
:root.bl-theme-purple-blue.bl-color-scheme-light,
|
|
534
544
|
:root.bl-theme-candy.bl-color-scheme-light,
|
|
535
545
|
:root.bl-theme-earth.bl-color-scheme-light,
|
|
@@ -537,6 +547,7 @@
|
|
|
537
547
|
:root.bl-theme-sunset.bl-color-scheme-light,
|
|
538
548
|
:root.bl-theme-high-contrast.bl-color-scheme-light,
|
|
539
549
|
:root.bl-theme-light,
|
|
550
|
+
:root.bl-theme-midnight-light,
|
|
540
551
|
:root.bl-theme-purple-blue-light,
|
|
541
552
|
:root.bl-theme-anime-light,
|
|
542
553
|
:root.bl-theme-candy-light,
|
|
@@ -655,10 +666,14 @@ input[type=file] {
|
|
|
655
666
|
}
|
|
656
667
|
|
|
657
668
|
input:focus, select:focus, textarea:focus {
|
|
658
|
-
outline: none;
|
|
659
669
|
border-color: var(--bl-accent);
|
|
660
670
|
}
|
|
661
671
|
|
|
672
|
+
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
|
|
673
|
+
outline: 2px solid var(--bl-accent);
|
|
674
|
+
outline-offset: 2px;
|
|
675
|
+
}
|
|
676
|
+
|
|
662
677
|
button {
|
|
663
678
|
padding: 3px 10px;
|
|
664
679
|
background: var(--bl-surface);
|
|
@@ -736,6 +751,74 @@ button:disabled { opacity: 0.4; cursor: default; }
|
|
|
736
751
|
gap: 8px;
|
|
737
752
|
}
|
|
738
753
|
|
|
754
|
+
.bl-dialog {
|
|
755
|
+
width: min(460px, calc(100vw - 24px));
|
|
756
|
+
max-height: calc(100dvh - 24px);
|
|
757
|
+
overflow: hidden;
|
|
758
|
+
padding: 0;
|
|
759
|
+
border: 1px solid var(--bl-border-control);
|
|
760
|
+
border-radius: var(--bl-radius-md);
|
|
761
|
+
color: var(--bl-text);
|
|
762
|
+
background: var(--bl-bg-strong);
|
|
763
|
+
box-shadow: var(--bl-shadow-popover);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
.bl-dialog::backdrop { background: var(--bl-overlay); }
|
|
767
|
+
|
|
768
|
+
.bl-dialog-form {
|
|
769
|
+
display: flex;
|
|
770
|
+
flex-direction: column;
|
|
771
|
+
max-height: calc(100dvh - 24px);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
.bl-dialog-header,
|
|
775
|
+
.bl-dialog-body,
|
|
776
|
+
.bl-dialog-actions {
|
|
777
|
+
padding: 14px;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
.bl-dialog-header {
|
|
781
|
+
border-bottom: 1px solid var(--bl-border);
|
|
782
|
+
background: var(--bl-bg);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
.bl-dialog-header h2 {
|
|
786
|
+
margin: 0;
|
|
787
|
+
color: var(--bl-text);
|
|
788
|
+
font-size: var(--bl-text-xl);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
.bl-dialog-body {
|
|
792
|
+
display: grid;
|
|
793
|
+
gap: 12px;
|
|
794
|
+
overflow-y: auto;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
.bl-dialog-message {
|
|
798
|
+
margin: 0;
|
|
799
|
+
color: var(--bl-text-soft);
|
|
800
|
+
font-size: var(--bl-text-md);
|
|
801
|
+
line-height: 1.5;
|
|
802
|
+
overflow-wrap: anywhere;
|
|
803
|
+
white-space: pre-wrap;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
.bl-dialog-field {
|
|
807
|
+
display: grid;
|
|
808
|
+
gap: 5px;
|
|
809
|
+
font-size: var(--bl-text-sm);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
.bl-dialog-field input { width: 100%; }
|
|
813
|
+
|
|
814
|
+
.bl-dialog-actions {
|
|
815
|
+
display: flex;
|
|
816
|
+
justify-content: flex-end;
|
|
817
|
+
gap: 8px;
|
|
818
|
+
border-top: 1px solid var(--bl-border);
|
|
819
|
+
background: var(--bl-bg);
|
|
820
|
+
}
|
|
821
|
+
|
|
739
822
|
#toolbar {
|
|
740
823
|
display: flex;
|
|
741
824
|
flex-direction: column;
|
|
@@ -903,6 +986,19 @@ button:disabled { opacity: 0.4; cursor: default; }
|
|
|
903
986
|
padding: 6px;
|
|
904
987
|
}
|
|
905
988
|
|
|
989
|
+
.client-menu-section-title {
|
|
990
|
+
padding: 10px 10px 3px;
|
|
991
|
+
color: var(--bl-accent-strong);
|
|
992
|
+
font-size: var(--bl-text-xs);
|
|
993
|
+
font-weight: bold;
|
|
994
|
+
letter-spacing: 0.5px;
|
|
995
|
+
text-transform: uppercase;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
.client-menu-section-title:first-child {
|
|
999
|
+
padding-top: 4px;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
906
1002
|
.client-menu-item {
|
|
907
1003
|
display: grid;
|
|
908
1004
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
@@ -945,6 +1041,18 @@ button:disabled { opacity: 0.4; cursor: default; }
|
|
|
945
1041
|
margin-left: 6px;
|
|
946
1042
|
}
|
|
947
1043
|
|
|
1044
|
+
.client-menu-recommended {
|
|
1045
|
+
display: inline-block;
|
|
1046
|
+
margin-left: 7px;
|
|
1047
|
+
padding: 1px 5px;
|
|
1048
|
+
border: 1px solid var(--bl-ok);
|
|
1049
|
+
border-radius: var(--bl-radius-sm);
|
|
1050
|
+
color: var(--bl-ok);
|
|
1051
|
+
font-size: var(--bl-text-xs);
|
|
1052
|
+
font-weight: normal;
|
|
1053
|
+
vertical-align: middle;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
948
1056
|
.client-menu-item-desc {
|
|
949
1057
|
color: var(--bl-text-soft);
|
|
950
1058
|
font-size: var(--bl-text-sm);
|
|
@@ -1582,6 +1690,17 @@ textarea {
|
|
|
1582
1690
|
min-height: 30px;
|
|
1583
1691
|
}
|
|
1584
1692
|
|
|
1693
|
+
:is(.bl-page-welcome, .bl-page-toon, .bl-page-character, .bl-page-web-tui, .bl-page-web-repl) {
|
|
1694
|
+
--bl-text-xs: 12px;
|
|
1695
|
+
--bl-text-sm: 14px;
|
|
1696
|
+
--bl-text-md: 16px;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
:is(.bl-page-welcome, .bl-page-toon, .bl-page-character, .bl-page-web-tui, .bl-page-web-repl)
|
|
1700
|
+
:is(button, input[type=file], input[type=text], input[type=number], select, .button-link) {
|
|
1701
|
+
min-height: 44px;
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1585
1704
|
.client-menu-backdrop {
|
|
1586
1705
|
padding: 12px;
|
|
1587
1706
|
}
|
package/assets/bunnyland-ui.js
CHANGED
|
@@ -5,16 +5,19 @@
|
|
|
5
5
|
const THEME_CLASS_PREFIX = 'bl-theme-';
|
|
6
6
|
const COLOR_SCHEME_KEY = 'bunnyland.color-scheme';
|
|
7
7
|
const COLOR_SCHEME_CLASS_PREFIX = 'bl-color-scheme-';
|
|
8
|
-
const DEFAULT_THEME = '
|
|
8
|
+
const DEFAULT_THEME = 'midnight';
|
|
9
9
|
const THEME_ALIASES = {
|
|
10
10
|
anime: 'candy',
|
|
11
11
|
'anime-dark': 'candy-dark',
|
|
12
12
|
'anime-light': 'candy-light',
|
|
13
|
-
dark: '
|
|
14
|
-
light: '
|
|
13
|
+
dark: 'midnight-dark',
|
|
14
|
+
light: 'midnight-light',
|
|
15
|
+
'purple-blue': 'midnight',
|
|
16
|
+
'purple-blue-dark': 'midnight-dark',
|
|
17
|
+
'purple-blue-light': 'midnight-light',
|
|
15
18
|
};
|
|
16
19
|
const DEFAULT_THEME_OPTIONS = [
|
|
17
|
-
{ value: '
|
|
20
|
+
{ value: 'midnight', label: 'Midnight Blue / Lavender' },
|
|
18
21
|
{ value: 'candy', label: 'Candy Pink / Cyan' },
|
|
19
22
|
{ value: 'earth', label: 'Earth Green / Gold' },
|
|
20
23
|
{ value: 'ocean', label: 'Ocean Teal / Coral' },
|
|
@@ -31,6 +34,7 @@
|
|
|
31
34
|
const boundThemeSelects = new Set();
|
|
32
35
|
const boundColorSchemeSelects = new Set();
|
|
33
36
|
const CLIENT_MENU_SEEN_KEY = 'bunnyland.clientMenu.seen';
|
|
37
|
+
let actionDialogQueue = Promise.resolve();
|
|
34
38
|
let clientMenuBaseUrl = '';
|
|
35
39
|
// Admin tools order: World Generator, World Graph, editor tools alphabetically, then miscellaneous tools.
|
|
36
40
|
const CLIENT_MENU_ITEMS = [
|
|
@@ -39,6 +43,7 @@
|
|
|
39
43
|
title: 'Welcome',
|
|
40
44
|
label: 'Start here',
|
|
41
45
|
description: 'Project overview, docs, admin notes, and client chooser.',
|
|
46
|
+
group: 'Bunnyland',
|
|
42
47
|
supportsServer: true,
|
|
43
48
|
},
|
|
44
49
|
{
|
|
@@ -46,21 +51,25 @@
|
|
|
46
51
|
title: 'Bunnyland.dev',
|
|
47
52
|
label: 'Website',
|
|
48
53
|
description: 'Project homepage, feature overview, guides, and public docs.',
|
|
54
|
+
group: 'Bunnyland',
|
|
49
55
|
supportsServer: false,
|
|
50
56
|
},
|
|
51
|
-
{
|
|
52
|
-
href: 'toon-client.html',
|
|
53
|
-
title: 'Toon Client',
|
|
54
|
-
label: 'Player room view',
|
|
55
|
-
description: 'Claim a character and play from the room-focused visual client.',
|
|
56
|
-
supportsFocus: true,
|
|
57
|
-
supportsServer: true,
|
|
58
|
-
},
|
|
59
57
|
{
|
|
60
58
|
href: 'web-tui.html',
|
|
61
59
|
title: 'Web TUI',
|
|
62
60
|
label: 'Player action menu',
|
|
63
61
|
description: 'Claim a character and play from the terminal TUI-style browser client.',
|
|
62
|
+
group: 'Player clients',
|
|
63
|
+
recommended: true,
|
|
64
|
+
supportsFocus: true,
|
|
65
|
+
supportsServer: true,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
href: 'toon-client.html',
|
|
69
|
+
title: 'Toon Client',
|
|
70
|
+
label: 'Player room view',
|
|
71
|
+
description: 'Claim a character and play from the room-focused visual client.',
|
|
72
|
+
group: 'Player clients',
|
|
64
73
|
supportsFocus: true,
|
|
65
74
|
supportsServer: true,
|
|
66
75
|
},
|
|
@@ -69,6 +78,7 @@
|
|
|
69
78
|
title: 'Web REPL',
|
|
70
79
|
label: 'Text-based play',
|
|
71
80
|
description: 'Claim a character and play with typed commands in the browser.',
|
|
81
|
+
group: 'Player clients',
|
|
72
82
|
supportsFocus: true,
|
|
73
83
|
supportsServer: true,
|
|
74
84
|
},
|
|
@@ -77,6 +87,7 @@
|
|
|
77
87
|
title: 'Character Profile',
|
|
78
88
|
label: 'Profile and chat',
|
|
79
89
|
description: 'Open a character profile, inspect live state, and chat in character.',
|
|
90
|
+
group: 'Player clients',
|
|
80
91
|
supportsFocus: true,
|
|
81
92
|
supportsServer: true,
|
|
82
93
|
},
|
|
@@ -85,6 +96,7 @@
|
|
|
85
96
|
title: 'World Generator',
|
|
86
97
|
label: 'Admin generator',
|
|
87
98
|
description: 'Generate or replace a live world using enabled server generators.',
|
|
99
|
+
group: 'Builder & admin',
|
|
88
100
|
supportsServer: true,
|
|
89
101
|
admin: true,
|
|
90
102
|
},
|
|
@@ -93,6 +105,7 @@
|
|
|
93
105
|
title: 'World Graph',
|
|
94
106
|
label: 'Graph editor',
|
|
95
107
|
description: 'Browse and extend the ECS world graph from a snapshot or live server.',
|
|
108
|
+
group: 'Builder & admin',
|
|
96
109
|
supportsServer: true,
|
|
97
110
|
admin: true,
|
|
98
111
|
},
|
|
@@ -101,6 +114,7 @@
|
|
|
101
114
|
title: 'Behavior Editor',
|
|
102
115
|
label: 'Behavior trees',
|
|
103
116
|
description: 'Author behavior-tree JSON for behavioral controllers and register it live.',
|
|
117
|
+
group: 'Builder & admin',
|
|
104
118
|
supportsServer: true,
|
|
105
119
|
admin: true,
|
|
106
120
|
},
|
|
@@ -109,6 +123,7 @@
|
|
|
109
123
|
title: 'Memory Editor',
|
|
110
124
|
label: 'Memory editor',
|
|
111
125
|
description: 'Inspect and edit character memory collections on a live server.',
|
|
126
|
+
group: 'Builder & admin',
|
|
112
127
|
supportsServer: true,
|
|
113
128
|
admin: true,
|
|
114
129
|
},
|
|
@@ -117,6 +132,7 @@
|
|
|
117
132
|
title: 'Script Editor',
|
|
118
133
|
label: 'Automation scripts',
|
|
119
134
|
description: 'Create and validate script JSON blocks against a snapshot.',
|
|
135
|
+
group: 'Builder & admin',
|
|
120
136
|
supportsServer: false,
|
|
121
137
|
admin: true,
|
|
122
138
|
},
|
|
@@ -125,6 +141,7 @@
|
|
|
125
141
|
title: 'World Editor',
|
|
126
142
|
label: 'Admin editor',
|
|
127
143
|
description: 'Edit entities, components, relationships, fragments, and live snapshots.',
|
|
144
|
+
group: 'Builder & admin',
|
|
128
145
|
supportsServer: true,
|
|
129
146
|
admin: true,
|
|
130
147
|
},
|
|
@@ -133,6 +150,7 @@
|
|
|
133
150
|
title: 'Event Stream',
|
|
134
151
|
label: 'Event viewer',
|
|
135
152
|
description: 'Watch the live world event feed with expandable records and entity references.',
|
|
153
|
+
group: 'Builder & admin',
|
|
136
154
|
supportsServer: true,
|
|
137
155
|
admin: true,
|
|
138
156
|
},
|
|
@@ -141,6 +159,7 @@
|
|
|
141
159
|
title: 'Trace Analyzer',
|
|
142
160
|
label: 'Trace inspection',
|
|
143
161
|
description: 'Inspect live Tempo traces or load JSON and JSONL trace artifacts.',
|
|
162
|
+
group: 'Builder & admin',
|
|
144
163
|
supportsServer: true,
|
|
145
164
|
admin: true,
|
|
146
165
|
},
|
|
@@ -473,6 +492,138 @@
|
|
|
473
492
|
return url.origin === location.origin ? '' : ' target="_blank" rel="noopener"';
|
|
474
493
|
}
|
|
475
494
|
|
|
495
|
+
function ensureActionDialog() {
|
|
496
|
+
let dialog = document.getElementById('bl-action-dialog');
|
|
497
|
+
if (dialog) return dialog;
|
|
498
|
+
dialog = document.createElement('dialog');
|
|
499
|
+
dialog.id = 'bl-action-dialog';
|
|
500
|
+
dialog.className = 'bl-dialog';
|
|
501
|
+
dialog.setAttribute('aria-labelledby', 'bl-action-dialog-title');
|
|
502
|
+
document.body.appendChild(dialog);
|
|
503
|
+
return dialog;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function actionDialog({
|
|
507
|
+
cancelLabel = 'Cancel',
|
|
508
|
+
cancellable = true,
|
|
509
|
+
confirmLabel = 'OK',
|
|
510
|
+
fields = [],
|
|
511
|
+
message = '',
|
|
512
|
+
title = 'Bunnyland',
|
|
513
|
+
tone = 'default',
|
|
514
|
+
} = {}) {
|
|
515
|
+
const open = () => new Promise((resolve) => {
|
|
516
|
+
const dialog = ensureActionDialog();
|
|
517
|
+
const previousFocus = document.activeElement;
|
|
518
|
+
dialog.innerHTML = `
|
|
519
|
+
<form class="bl-dialog-form" method="dialog">
|
|
520
|
+
<div class="bl-dialog-header"><h2 id="bl-action-dialog-title">${escapeHtml(title)}</h2></div>
|
|
521
|
+
<div class="bl-dialog-body">
|
|
522
|
+
${message ? `<p class="bl-dialog-message">${escapeHtml(message)}</p>` : ''}
|
|
523
|
+
${fields.map((field, index) => `
|
|
524
|
+
<label class="bl-dialog-field">
|
|
525
|
+
<span>${escapeHtml(field.label || field.name || `Value ${index + 1}`)}</span>
|
|
526
|
+
<input name="${escapeHtml(field.name || `field-${index}`)}"
|
|
527
|
+
type="${escapeHtml(field.type || 'text')}"
|
|
528
|
+
value="${escapeHtml(field.value || '')}"
|
|
529
|
+
${field.autocomplete ? `autocomplete="${escapeHtml(field.autocomplete)}"` : ''}
|
|
530
|
+
${field.placeholder ? `placeholder="${escapeHtml(field.placeholder)}"` : ''}
|
|
531
|
+
${field.required ? 'required' : ''}>
|
|
532
|
+
</label>
|
|
533
|
+
`).join('')}
|
|
534
|
+
</div>
|
|
535
|
+
<div class="bl-dialog-actions">
|
|
536
|
+
${cancellable ? `<button class="bl-dialog-cancel" type="button">${escapeHtml(cancelLabel)}</button>` : ''}
|
|
537
|
+
<button class="bl-dialog-confirm ${tone === 'danger' ? 'bl-button-danger' : 'bl-button-primary'}" type="submit">${escapeHtml(confirmLabel)}</button>
|
|
538
|
+
</div>
|
|
539
|
+
</form>
|
|
540
|
+
`;
|
|
541
|
+
const form = dialog.querySelector('.bl-dialog-form');
|
|
542
|
+
const cancel = dialog.querySelector('.bl-dialog-cancel');
|
|
543
|
+
let settled = false;
|
|
544
|
+
|
|
545
|
+
const finish = (value) => {
|
|
546
|
+
if (settled) return;
|
|
547
|
+
settled = true;
|
|
548
|
+
dialog.removeEventListener('cancel', onCancel);
|
|
549
|
+
dialog.removeEventListener('click', onBackdropClick);
|
|
550
|
+
if (dialog.open && typeof dialog.close === 'function') dialog.close();
|
|
551
|
+
else dialog.removeAttribute('open');
|
|
552
|
+
previousFocus?.focus?.();
|
|
553
|
+
resolve(value);
|
|
554
|
+
};
|
|
555
|
+
const onCancel = (event) => {
|
|
556
|
+
event.preventDefault();
|
|
557
|
+
if (cancellable) finish(null);
|
|
558
|
+
};
|
|
559
|
+
const onBackdropClick = (event) => {
|
|
560
|
+
if (cancellable && event.target === dialog) finish(null);
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
form.addEventListener('submit', (event) => {
|
|
564
|
+
event.preventDefault();
|
|
565
|
+
const values = {};
|
|
566
|
+
for (const field of fields) {
|
|
567
|
+
const input = form.elements.namedItem(field.name);
|
|
568
|
+
values[field.name] = input?.value || '';
|
|
569
|
+
}
|
|
570
|
+
finish(values);
|
|
571
|
+
});
|
|
572
|
+
cancel?.addEventListener('click', () => finish(null));
|
|
573
|
+
dialog.addEventListener('cancel', onCancel);
|
|
574
|
+
dialog.addEventListener('click', onBackdropClick);
|
|
575
|
+
if (typeof dialog.showModal === 'function') dialog.showModal();
|
|
576
|
+
else dialog.setAttribute('open', '');
|
|
577
|
+
(dialog.querySelector('input') || dialog.querySelector('.bl-dialog-confirm'))?.focus();
|
|
578
|
+
});
|
|
579
|
+
const queued = actionDialogQueue.then(open, open);
|
|
580
|
+
actionDialogQueue = queued.then(() => undefined, () => undefined);
|
|
581
|
+
return queued;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async function promptDialog(message, options = {}) {
|
|
585
|
+
const fieldName = 'value';
|
|
586
|
+
const values = await actionDialog({
|
|
587
|
+
...options,
|
|
588
|
+
fields: [{
|
|
589
|
+
autocomplete: options.autocomplete,
|
|
590
|
+
label: options.label || 'Value',
|
|
591
|
+
name: fieldName,
|
|
592
|
+
placeholder: options.placeholder,
|
|
593
|
+
required: options.required,
|
|
594
|
+
type: options.type || 'text',
|
|
595
|
+
value: options.value || '',
|
|
596
|
+
}],
|
|
597
|
+
message,
|
|
598
|
+
});
|
|
599
|
+
return values ? values[fieldName] : null;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
async function confirmDialog(message, options = {}) {
|
|
603
|
+
return Boolean(await actionDialog({
|
|
604
|
+
...options,
|
|
605
|
+
confirmLabel: options.confirmLabel || 'Confirm',
|
|
606
|
+
fields: [],
|
|
607
|
+
message,
|
|
608
|
+
}));
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
async function credentialsDialog(options = {}) {
|
|
612
|
+
return actionDialog({
|
|
613
|
+
...options,
|
|
614
|
+
confirmLabel: options.confirmLabel || 'Sign in',
|
|
615
|
+
fields: [
|
|
616
|
+
{ autocomplete: 'username', label: options.usernameLabel || 'Username', name: 'username', required: true },
|
|
617
|
+
{ autocomplete: 'current-password', label: options.passwordLabel || 'Password', name: 'password', required: true, type: 'password' },
|
|
618
|
+
],
|
|
619
|
+
title: options.title || 'Sign in to Bunnyland',
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
async function alertDialog(message, options = {}) {
|
|
624
|
+
await actionDialog({ ...options, cancellable: false, fields: [], message });
|
|
625
|
+
}
|
|
626
|
+
|
|
476
627
|
function ensureClientMenu() {
|
|
477
628
|
let dialog = document.getElementById('client-menu-dialog');
|
|
478
629
|
if (dialog) return dialog;
|
|
@@ -501,12 +652,14 @@
|
|
|
501
652
|
<button class="client-menu-close" type="button" aria-label="Close client menu">x</button>
|
|
502
653
|
</div>
|
|
503
654
|
<div class="client-menu-list">
|
|
504
|
-
${CLIENT_MENU_ITEMS.map((item) => {
|
|
655
|
+
${CLIENT_MENU_ITEMS.map((item, index) => {
|
|
505
656
|
const active = item.href === current || (current === '' && item.href === 'index.html');
|
|
657
|
+
const previousGroup = CLIENT_MENU_ITEMS[index - 1]?.group;
|
|
506
658
|
return `
|
|
659
|
+
${item.group !== previousGroup ? `<div class="client-menu-section-title">${escapeHtml(item.group)}</div>` : ''}
|
|
507
660
|
<a class="client-menu-item ${active ? 'active' : ''}" href="${escapeHtml(clientHref(item))}"${clientTargetAttrs(item)}>
|
|
508
661
|
<span class="client-menu-item-main">
|
|
509
|
-
<span class="client-menu-item-title">${escapeHtml(item.title)}${item.admin ? '<span class="client-menu-admin-badge" title="Requires authentication" aria-label="Requires authentication">●</span>' : ''}</span>
|
|
662
|
+
<span class="client-menu-item-title">${escapeHtml(item.title)}${item.recommended ? '<span class="client-menu-recommended">Recommended</span>' : ''}${item.admin ? '<span class="client-menu-admin-badge" title="Requires authentication" aria-label="Requires authentication">●</span>' : ''}</span>
|
|
510
663
|
<span class="client-menu-item-desc">${escapeHtml(item.description)}</span>
|
|
511
664
|
</span>
|
|
512
665
|
<span class="client-menu-item-label">${escapeHtml(active ? 'Current' : item.label)}</span>
|
|
@@ -765,6 +918,7 @@
|
|
|
765
918
|
}
|
|
766
919
|
|
|
767
920
|
window.BunnylandUI = {
|
|
921
|
+
alertDialog,
|
|
768
922
|
bindColorSchemeSelect,
|
|
769
923
|
bindTagEditor,
|
|
770
924
|
bindSearchDropdown,
|
|
@@ -774,12 +928,15 @@
|
|
|
774
928
|
currentColorScheme,
|
|
775
929
|
escapeHtml,
|
|
776
930
|
currentTheme,
|
|
931
|
+
confirmDialog,
|
|
932
|
+
credentialsDialog,
|
|
777
933
|
initClientMenu,
|
|
778
934
|
initHelp,
|
|
779
935
|
initTheme,
|
|
780
936
|
loadConfig,
|
|
781
937
|
normalizeTags,
|
|
782
938
|
normalizeTheme,
|
|
939
|
+
promptDialog,
|
|
783
940
|
registerThemeOption,
|
|
784
941
|
registerThemeOptions,
|
|
785
942
|
renderTagEditorTags,
|
package/dist/theme.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export declare const THEME_KEY = "bunnyland.theme";
|
|
|
2
2
|
export declare const THEME_CLASS_PREFIX = "bl-theme-";
|
|
3
3
|
export declare const COLOR_SCHEME_KEY = "bunnyland.color-scheme";
|
|
4
4
|
export declare const COLOR_SCHEME_CLASS_PREFIX = "bl-color-scheme-";
|
|
5
|
-
export declare const DEFAULT_THEME = "
|
|
5
|
+
export declare const DEFAULT_THEME = "midnight";
|
|
6
6
|
export declare const THEME_CHANGE_EVENT = "bunnyland:themechange";
|
|
7
7
|
export interface ThemeOption {
|
|
8
8
|
value: string;
|
package/dist/theme.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,SAAS,oBAAoB,CAAC;AAC3C,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAC9C,eAAO,MAAM,gBAAgB,2BAA2B,CAAC;AACzD,eAAO,MAAM,yBAAyB,qBAAqB,CAAC;AAC5D,eAAO,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,SAAS,oBAAoB,CAAC;AAC3C,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAC9C,eAAO,MAAM,gBAAgB,2BAA2B,CAAC;AACzD,eAAO,MAAM,yBAAyB,qBAAqB,CAAC;AAC5D,eAAO,MAAM,aAAa,aAAa,CAAC;AACxC,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;AAE1D,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpD,eAAO,MAAM,qBAAqB,EAAE,WAAW,EAO9C,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,WAAW,EAAyD,CAAC;AAyEjG,wBAAgB,eAAe,CAAC,MAAM,SAAoC,GAAG,MAAM,GAAG,IAAI,CAKzF;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGtE;AAED,wBAAgB,YAAY,CAAC,IAAI,GAAE,WAAsC,GAAG,MAAM,CAEjF;AAED,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,WAAsC,GAAG,WAAW,CAE5F;AAkCD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAsC,GAAG,WAAW,CAEtG;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAsC,GAAG,MAAM,CAK3F;AAED,wBAAgB,SAAS,CACvB,IAAI,GAAE,WAAsC,EAC5C,YAAY,SAAgB,EAC5B,MAAM,SAAoC,GACzC,MAAM,CAUR;AAED,wBAAgB,YAAY,IAAI,WAAW,EAAE,CAE5C;AAED,wBAAgB,kBAAkB,IAAI,WAAW,EAAE,CAElD;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,GAAG,IAAI,CAQ3E;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,EAAE,CAK7F;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,GAAG;IAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GAAG,IAAI,CAY9G;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,GAAG;IAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GAAG,IAAI,CAWpH"}
|
package/dist/theme.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { escapeHtml as e, storageGet as t, storageSet as n } from "./widgets.js";
|
|
2
2
|
//#region src/theme.ts
|
|
3
|
-
var r = "bunnyland.theme", i = "bl-theme-", a = "bunnyland.color-scheme", o = "bl-color-scheme-", s = "
|
|
3
|
+
var r = "bunnyland.theme", i = "bl-theme-", a = "bunnyland.color-scheme", o = "bl-color-scheme-", s = "midnight", c = "bunnyland:themechange", l = [
|
|
4
4
|
{
|
|
5
|
-
value: "
|
|
6
|
-
label: "
|
|
5
|
+
value: "midnight",
|
|
6
|
+
label: "Midnight Blue / Lavender"
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
9
|
value: "candy",
|
|
@@ -29,8 +29,11 @@ var r = "bunnyland.theme", i = "bl-theme-", a = "bunnyland.color-scheme", o = "b
|
|
|
29
29
|
anime: "candy",
|
|
30
30
|
"anime-dark": "candy-dark",
|
|
31
31
|
"anime-light": "candy-light",
|
|
32
|
-
dark: "
|
|
33
|
-
light: "
|
|
32
|
+
dark: "midnight-dark",
|
|
33
|
+
light: "midnight-light",
|
|
34
|
+
"purple-blue": "midnight",
|
|
35
|
+
"purple-blue-dark": "midnight-dark",
|
|
36
|
+
"purple-blue-light": "midnight-light"
|
|
34
37
|
}, f = [
|
|
35
38
|
{
|
|
36
39
|
value: "auto",
|
|
@@ -46,7 +49,7 @@ var r = "bunnyland.theme", i = "bl-theme-", a = "bunnyland.color-scheme", o = "b
|
|
|
46
49
|
}
|
|
47
50
|
], p = /^[a-z0-9][a-z0-9-]*$/, m = /* @__PURE__ */ new Set(), h = /* @__PURE__ */ new Set();
|
|
48
51
|
function g(e) {
|
|
49
|
-
let t = d[String(e || "
|
|
52
|
+
let t = d[String(e || "midnight").trim()] || String(e || "midnight").trim();
|
|
50
53
|
if (v(t)) return {
|
|
51
54
|
theme: t,
|
|
52
55
|
scheme: null
|
|
@@ -131,7 +134,7 @@ function N(e, t = document.documentElement) {
|
|
|
131
134
|
return n.scheme && j(n.scheme, t, !0), A(E(n.theme), t, !0);
|
|
132
135
|
}
|
|
133
136
|
function P(e = document.documentElement, n = s, i = globalThis.location?.search || "") {
|
|
134
|
-
let o = new URLSearchParams(i).get("theme"), c = T(i), l = t(r), u = g(c && o || l || n || "
|
|
137
|
+
let o = new URLSearchParams(i).get("theme"), c = T(i), l = t(r), u = g(c && o || l || n || "midnight"), d = t(a);
|
|
135
138
|
return j(u.scheme || S(d), e, !!(u.scheme || d)), A(E(u.theme), e, !!(c || l && v(u.theme)));
|
|
136
139
|
}
|
|
137
140
|
function F() {
|
package/dist/theme.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.js","names":[],"sources":["../src/theme.ts"],"sourcesContent":["import { escapeHtml, storageGet, storageSet } from './widgets';\n\nexport const THEME_KEY = 'bunnyland.theme';\nexport const THEME_CLASS_PREFIX = 'bl-theme-';\nexport const COLOR_SCHEME_KEY = 'bunnyland.color-scheme';\nexport const COLOR_SCHEME_CLASS_PREFIX = 'bl-color-scheme-';\nexport const DEFAULT_THEME = 'purple-blue';\nexport const THEME_CHANGE_EVENT = 'bunnyland:themechange';\n\nexport interface ThemeOption {\n value: string;\n label: string;\n}\n\nexport type ColorScheme = 'auto' | 'dark' | 'light';\n\nexport const DEFAULT_THEME_OPTIONS: ThemeOption[] = [\n { value: 'purple-blue', label: 'Purple / Blue' },\n { value: 'candy', label: 'Candy Pink / Cyan' },\n { value: 'earth', label: 'Earth Green / Gold' },\n { value: 'ocean', label: 'Ocean Teal / Coral' },\n { value: 'sunset', label: 'Sunset Orange / Plum' },\n { value: 'high-contrast', label: 'High Contrast' },\n];\n\nexport const THEME_OPTIONS: ThemeOption[] = DEFAULT_THEME_OPTIONS.map(option => ({ ...option }));\n\nconst THEME_ALIASES: Record<string, string> = {\n anime: 'candy',\n 'anime-dark': 'candy-dark',\n 'anime-light': 'candy-light',\n dark: 'purple-blue-dark',\n light: 'purple-blue-light',\n};\n\nconst COLOR_SCHEME_OPTIONS: ThemeOption[] = [\n { value: 'auto', label: 'Auto (System)' },\n { value: 'dark', label: 'Dark' },\n { value: 'light', label: 'Light' },\n];\n\nconst THEME_VALUE_PATTERN = /^[a-z0-9][a-z0-9-]*$/;\nconst boundThemeSelects = new Set<HTMLSelectElement>();\nconst boundColorSchemeSelects = new Set<HTMLSelectElement>();\n\nfunction parseThemeSelection(name: string | null | undefined): { scheme: ColorScheme | null; theme: string } {\n const raw = THEME_ALIASES[String(name || DEFAULT_THEME).trim()] || String(name || DEFAULT_THEME).trim();\n if (isKnownTheme(raw)) return { theme: raw, scheme: null };\n const match = raw.match(/^(.*)-(dark|light)$/);\n if (match && isKnownTheme(match[1])) return { theme: match[1], scheme: match[2] as ColorScheme };\n return { theme: raw, scheme: null };\n}\n\nfunction normalizeThemeValue(name: string | null | undefined): string {\n return parseThemeSelection(name).theme;\n}\n\nfunction isKnownTheme(name: string): boolean {\n return THEME_OPTIONS.some(option => option.value === name);\n}\n\nfunction sanitizeThemeOption(option: ThemeOption): ThemeOption | null {\n const value = String(option?.value || '').trim();\n if (!THEME_VALUE_PATTERN.test(value)) return null;\n const label = String(option?.label || value).trim() || value;\n return { value, label };\n}\n\nfunction renderThemeSelect(select: HTMLSelectElement): void {\n const theme = currentTheme();\n select.innerHTML = themeOptions().map(option => `\n <option value=\"${escapeHtml(option.value)}\">${escapeHtml(option.label)}</option>\n `).join('');\n select.value = theme;\n}\n\nfunction refreshThemeSelects(): void {\n for (const select of boundThemeSelects) renderThemeSelect(select);\n}\n\nfunction normalizeColorSchemeValue(name: string | null | undefined): ColorScheme {\n return name === 'dark' || name === 'light' ? name : 'auto';\n}\n\nfunction renderColorSchemeSelect(select: HTMLSelectElement): void {\n select.innerHTML = COLOR_SCHEME_OPTIONS.map(option => `\n <option value=\"${option.value}\">${option.label}</option>\n `).join('');\n select.value = currentColorScheme();\n}\n\nfunction refreshColorSchemeSelects(): void {\n for (const select of boundColorSchemeSelects) renderColorSchemeSelect(select);\n}\n\nexport function themeFromSearch(search = globalThis.location?.search || ''): string | null {\n const requested = new URLSearchParams(search).get('theme');\n if (!requested) return null;\n const theme = normalizeThemeValue(requested);\n return isKnownTheme(theme) ? theme : null;\n}\n\nexport function normalizeTheme(name: string | null | undefined): string {\n const theme = normalizeThemeValue(name);\n return isKnownTheme(theme) ? theme : DEFAULT_THEME;\n}\n\nexport function currentTheme(root: HTMLElement = document.documentElement): string {\n return normalizeTheme(root.dataset.theme);\n}\n\nexport function currentColorScheme(root: HTMLElement = document.documentElement): ColorScheme {\n return normalizeColorSchemeValue(root.dataset.colorScheme);\n}\n\nfunction dispatchThemeChange(root: HTMLElement): void {\n if (typeof root.dispatchEvent === 'function' && typeof CustomEvent === 'function') {\n root.dispatchEvent(new CustomEvent(THEME_CHANGE_EVENT, {\n detail: { colorScheme: currentColorScheme(root), theme: currentTheme(root) },\n }));\n }\n}\n\nfunction applyTheme(theme: string, root: HTMLElement, persist: boolean): string {\n for (const className of [...root.classList]) {\n if (className.startsWith(THEME_CLASS_PREFIX)) root.classList.remove(className);\n }\n root.classList.add(`${THEME_CLASS_PREFIX}${theme}`);\n root.dataset.theme = theme;\n if (persist) storageSet(THEME_KEY, theme);\n refreshThemeSelects();\n dispatchThemeChange(root);\n return theme;\n}\n\nfunction applyColorScheme(scheme: ColorScheme, root: HTMLElement, persist: boolean): ColorScheme {\n for (const className of [...root.classList]) {\n if (className.startsWith(COLOR_SCHEME_CLASS_PREFIX)) root.classList.remove(className);\n }\n if (scheme !== 'auto') root.classList.add(`${COLOR_SCHEME_CLASS_PREFIX}${scheme}`);\n root.dataset.colorScheme = scheme;\n if (persist) storageSet(COLOR_SCHEME_KEY, scheme);\n refreshColorSchemeSelects();\n dispatchThemeChange(root);\n return scheme;\n}\n\nexport function setColorScheme(name: string, root: HTMLElement = document.documentElement): ColorScheme {\n return applyColorScheme(normalizeColorSchemeValue(name), root, true);\n}\n\nexport function setTheme(name: string, root: HTMLElement = document.documentElement): string {\n const selection = parseThemeSelection(name);\n if (selection.scheme) applyColorScheme(selection.scheme, root, true);\n const theme = normalizeTheme(selection.theme);\n return applyTheme(theme, root, true);\n}\n\nexport function initTheme(\n root: HTMLElement = document.documentElement,\n defaultTheme = DEFAULT_THEME,\n search = globalThis.location?.search || '',\n): string {\n const linkedValue = new URLSearchParams(search).get('theme');\n const linkedTheme = themeFromSearch(search);\n const stored = storageGet(THEME_KEY);\n const selection = parseThemeSelection((linkedTheme && linkedValue) || stored || defaultTheme || DEFAULT_THEME);\n const storedScheme = storageGet(COLOR_SCHEME_KEY);\n const scheme = selection.scheme || normalizeColorSchemeValue(storedScheme);\n applyColorScheme(scheme, root, Boolean(selection.scheme || storedScheme));\n const theme = normalizeTheme(selection.theme);\n return applyTheme(theme, root, Boolean(linkedTheme || (stored && isKnownTheme(selection.theme))));\n}\n\nexport function themeOptions(): ThemeOption[] {\n return THEME_OPTIONS.map(option => ({ ...option }));\n}\n\nexport function colorSchemeOptions(): ThemeOption[] {\n return COLOR_SCHEME_OPTIONS.map(option => ({ ...option }));\n}\n\nexport function registerThemeOption(option: ThemeOption): ThemeOption | null {\n const theme = sanitizeThemeOption(option);\n if (!theme) return null;\n const index = THEME_OPTIONS.findIndex(existing => existing.value === theme.value);\n if (index === -1) THEME_OPTIONS.push(theme);\n else THEME_OPTIONS[index] = theme;\n refreshThemeSelects();\n return { ...theme };\n}\n\nexport function registerThemeOptions(options: ThemeOption[] | null | undefined): ThemeOption[] {\n if (!Array.isArray(options)) return [];\n return options\n .map(option => registerThemeOption(option))\n .filter((option): option is ThemeOption => option !== null);\n}\n\nexport function bindThemeSelect(select: HTMLSelectElement | null): { setValue: (value: string) => void } | null {\n if (!select) return null;\n boundThemeSelects.add(select);\n renderThemeSelect(select);\n select.value = initTheme();\n select.addEventListener('change', () => setTheme(select.value));\n return {\n setValue(value: string): void {\n select.value = normalizeTheme(value);\n setTheme(select.value);\n },\n };\n}\n\nexport function bindColorSchemeSelect(select: HTMLSelectElement | null): { setValue: (value: string) => void } | null {\n if (!select) return null;\n boundColorSchemeSelects.add(select);\n renderColorSchemeSelect(select);\n select.addEventListener('change', () => setColorScheme(select.value));\n return {\n setValue(value: string): void {\n select.value = normalizeColorSchemeValue(value);\n setColorScheme(select.value);\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"theme.js","names":[],"sources":["../src/theme.ts"],"sourcesContent":["import { escapeHtml, storageGet, storageSet } from './widgets';\n\nexport const THEME_KEY = 'bunnyland.theme';\nexport const THEME_CLASS_PREFIX = 'bl-theme-';\nexport const COLOR_SCHEME_KEY = 'bunnyland.color-scheme';\nexport const COLOR_SCHEME_CLASS_PREFIX = 'bl-color-scheme-';\nexport const DEFAULT_THEME = 'midnight';\nexport const THEME_CHANGE_EVENT = 'bunnyland:themechange';\n\nexport interface ThemeOption {\n value: string;\n label: string;\n}\n\nexport type ColorScheme = 'auto' | 'dark' | 'light';\n\nexport const DEFAULT_THEME_OPTIONS: ThemeOption[] = [\n { value: 'midnight', label: 'Midnight Blue / Lavender' },\n { value: 'candy', label: 'Candy Pink / Cyan' },\n { value: 'earth', label: 'Earth Green / Gold' },\n { value: 'ocean', label: 'Ocean Teal / Coral' },\n { value: 'sunset', label: 'Sunset Orange / Plum' },\n { value: 'high-contrast', label: 'High Contrast' },\n];\n\nexport const THEME_OPTIONS: ThemeOption[] = DEFAULT_THEME_OPTIONS.map(option => ({ ...option }));\n\nconst THEME_ALIASES: Record<string, string> = {\n anime: 'candy',\n 'anime-dark': 'candy-dark',\n 'anime-light': 'candy-light',\n dark: 'midnight-dark',\n light: 'midnight-light',\n 'purple-blue': 'midnight',\n 'purple-blue-dark': 'midnight-dark',\n 'purple-blue-light': 'midnight-light',\n};\n\nconst COLOR_SCHEME_OPTIONS: ThemeOption[] = [\n { value: 'auto', label: 'Auto (System)' },\n { value: 'dark', label: 'Dark' },\n { value: 'light', label: 'Light' },\n];\n\nconst THEME_VALUE_PATTERN = /^[a-z0-9][a-z0-9-]*$/;\nconst boundThemeSelects = new Set<HTMLSelectElement>();\nconst boundColorSchemeSelects = new Set<HTMLSelectElement>();\n\nfunction parseThemeSelection(name: string | null | undefined): { scheme: ColorScheme | null; theme: string } {\n const raw = THEME_ALIASES[String(name || DEFAULT_THEME).trim()] || String(name || DEFAULT_THEME).trim();\n if (isKnownTheme(raw)) return { theme: raw, scheme: null };\n const match = raw.match(/^(.*)-(dark|light)$/);\n if (match && isKnownTheme(match[1])) return { theme: match[1], scheme: match[2] as ColorScheme };\n return { theme: raw, scheme: null };\n}\n\nfunction normalizeThemeValue(name: string | null | undefined): string {\n return parseThemeSelection(name).theme;\n}\n\nfunction isKnownTheme(name: string): boolean {\n return THEME_OPTIONS.some(option => option.value === name);\n}\n\nfunction sanitizeThemeOption(option: ThemeOption): ThemeOption | null {\n const value = String(option?.value || '').trim();\n if (!THEME_VALUE_PATTERN.test(value)) return null;\n const label = String(option?.label || value).trim() || value;\n return { value, label };\n}\n\nfunction renderThemeSelect(select: HTMLSelectElement): void {\n const theme = currentTheme();\n select.innerHTML = themeOptions().map(option => `\n <option value=\"${escapeHtml(option.value)}\">${escapeHtml(option.label)}</option>\n `).join('');\n select.value = theme;\n}\n\nfunction refreshThemeSelects(): void {\n for (const select of boundThemeSelects) renderThemeSelect(select);\n}\n\nfunction normalizeColorSchemeValue(name: string | null | undefined): ColorScheme {\n return name === 'dark' || name === 'light' ? name : 'auto';\n}\n\nfunction renderColorSchemeSelect(select: HTMLSelectElement): void {\n select.innerHTML = COLOR_SCHEME_OPTIONS.map(option => `\n <option value=\"${option.value}\">${option.label}</option>\n `).join('');\n select.value = currentColorScheme();\n}\n\nfunction refreshColorSchemeSelects(): void {\n for (const select of boundColorSchemeSelects) renderColorSchemeSelect(select);\n}\n\nexport function themeFromSearch(search = globalThis.location?.search || ''): string | null {\n const requested = new URLSearchParams(search).get('theme');\n if (!requested) return null;\n const theme = normalizeThemeValue(requested);\n return isKnownTheme(theme) ? theme : null;\n}\n\nexport function normalizeTheme(name: string | null | undefined): string {\n const theme = normalizeThemeValue(name);\n return isKnownTheme(theme) ? theme : DEFAULT_THEME;\n}\n\nexport function currentTheme(root: HTMLElement = document.documentElement): string {\n return normalizeTheme(root.dataset.theme);\n}\n\nexport function currentColorScheme(root: HTMLElement = document.documentElement): ColorScheme {\n return normalizeColorSchemeValue(root.dataset.colorScheme);\n}\n\nfunction dispatchThemeChange(root: HTMLElement): void {\n if (typeof root.dispatchEvent === 'function' && typeof CustomEvent === 'function') {\n root.dispatchEvent(new CustomEvent(THEME_CHANGE_EVENT, {\n detail: { colorScheme: currentColorScheme(root), theme: currentTheme(root) },\n }));\n }\n}\n\nfunction applyTheme(theme: string, root: HTMLElement, persist: boolean): string {\n for (const className of [...root.classList]) {\n if (className.startsWith(THEME_CLASS_PREFIX)) root.classList.remove(className);\n }\n root.classList.add(`${THEME_CLASS_PREFIX}${theme}`);\n root.dataset.theme = theme;\n if (persist) storageSet(THEME_KEY, theme);\n refreshThemeSelects();\n dispatchThemeChange(root);\n return theme;\n}\n\nfunction applyColorScheme(scheme: ColorScheme, root: HTMLElement, persist: boolean): ColorScheme {\n for (const className of [...root.classList]) {\n if (className.startsWith(COLOR_SCHEME_CLASS_PREFIX)) root.classList.remove(className);\n }\n if (scheme !== 'auto') root.classList.add(`${COLOR_SCHEME_CLASS_PREFIX}${scheme}`);\n root.dataset.colorScheme = scheme;\n if (persist) storageSet(COLOR_SCHEME_KEY, scheme);\n refreshColorSchemeSelects();\n dispatchThemeChange(root);\n return scheme;\n}\n\nexport function setColorScheme(name: string, root: HTMLElement = document.documentElement): ColorScheme {\n return applyColorScheme(normalizeColorSchemeValue(name), root, true);\n}\n\nexport function setTheme(name: string, root: HTMLElement = document.documentElement): string {\n const selection = parseThemeSelection(name);\n if (selection.scheme) applyColorScheme(selection.scheme, root, true);\n const theme = normalizeTheme(selection.theme);\n return applyTheme(theme, root, true);\n}\n\nexport function initTheme(\n root: HTMLElement = document.documentElement,\n defaultTheme = DEFAULT_THEME,\n search = globalThis.location?.search || '',\n): string {\n const linkedValue = new URLSearchParams(search).get('theme');\n const linkedTheme = themeFromSearch(search);\n const stored = storageGet(THEME_KEY);\n const selection = parseThemeSelection((linkedTheme && linkedValue) || stored || defaultTheme || DEFAULT_THEME);\n const storedScheme = storageGet(COLOR_SCHEME_KEY);\n const scheme = selection.scheme || normalizeColorSchemeValue(storedScheme);\n applyColorScheme(scheme, root, Boolean(selection.scheme || storedScheme));\n const theme = normalizeTheme(selection.theme);\n return applyTheme(theme, root, Boolean(linkedTheme || (stored && isKnownTheme(selection.theme))));\n}\n\nexport function themeOptions(): ThemeOption[] {\n return THEME_OPTIONS.map(option => ({ ...option }));\n}\n\nexport function colorSchemeOptions(): ThemeOption[] {\n return COLOR_SCHEME_OPTIONS.map(option => ({ ...option }));\n}\n\nexport function registerThemeOption(option: ThemeOption): ThemeOption | null {\n const theme = sanitizeThemeOption(option);\n if (!theme) return null;\n const index = THEME_OPTIONS.findIndex(existing => existing.value === theme.value);\n if (index === -1) THEME_OPTIONS.push(theme);\n else THEME_OPTIONS[index] = theme;\n refreshThemeSelects();\n return { ...theme };\n}\n\nexport function registerThemeOptions(options: ThemeOption[] | null | undefined): ThemeOption[] {\n if (!Array.isArray(options)) return [];\n return options\n .map(option => registerThemeOption(option))\n .filter((option): option is ThemeOption => option !== null);\n}\n\nexport function bindThemeSelect(select: HTMLSelectElement | null): { setValue: (value: string) => void } | null {\n if (!select) return null;\n boundThemeSelects.add(select);\n renderThemeSelect(select);\n select.value = initTheme();\n select.addEventListener('change', () => setTheme(select.value));\n return {\n setValue(value: string): void {\n select.value = normalizeTheme(value);\n setTheme(select.value);\n },\n };\n}\n\nexport function bindColorSchemeSelect(select: HTMLSelectElement | null): { setValue: (value: string) => void } | null {\n if (!select) return null;\n boundColorSchemeSelects.add(select);\n renderColorSchemeSelect(select);\n select.addEventListener('change', () => setColorScheme(select.value));\n return {\n setValue(value: string): void {\n select.value = normalizeColorSchemeValue(value);\n setColorScheme(select.value);\n },\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/theme.ts
CHANGED
|
@@ -4,7 +4,7 @@ export const THEME_KEY = 'bunnyland.theme';
|
|
|
4
4
|
export const THEME_CLASS_PREFIX = 'bl-theme-';
|
|
5
5
|
export const COLOR_SCHEME_KEY = 'bunnyland.color-scheme';
|
|
6
6
|
export const COLOR_SCHEME_CLASS_PREFIX = 'bl-color-scheme-';
|
|
7
|
-
export const DEFAULT_THEME = '
|
|
7
|
+
export const DEFAULT_THEME = 'midnight';
|
|
8
8
|
export const THEME_CHANGE_EVENT = 'bunnyland:themechange';
|
|
9
9
|
|
|
10
10
|
export interface ThemeOption {
|
|
@@ -15,7 +15,7 @@ export interface ThemeOption {
|
|
|
15
15
|
export type ColorScheme = 'auto' | 'dark' | 'light';
|
|
16
16
|
|
|
17
17
|
export const DEFAULT_THEME_OPTIONS: ThemeOption[] = [
|
|
18
|
-
{ value: '
|
|
18
|
+
{ value: 'midnight', label: 'Midnight Blue / Lavender' },
|
|
19
19
|
{ value: 'candy', label: 'Candy Pink / Cyan' },
|
|
20
20
|
{ value: 'earth', label: 'Earth Green / Gold' },
|
|
21
21
|
{ value: 'ocean', label: 'Ocean Teal / Coral' },
|
|
@@ -29,8 +29,11 @@ const THEME_ALIASES: Record<string, string> = {
|
|
|
29
29
|
anime: 'candy',
|
|
30
30
|
'anime-dark': 'candy-dark',
|
|
31
31
|
'anime-light': 'candy-light',
|
|
32
|
-
dark: '
|
|
33
|
-
light: '
|
|
32
|
+
dark: 'midnight-dark',
|
|
33
|
+
light: 'midnight-light',
|
|
34
|
+
'purple-blue': 'midnight',
|
|
35
|
+
'purple-blue-dark': 'midnight-dark',
|
|
36
|
+
'purple-blue-light': 'midnight-light',
|
|
34
37
|
};
|
|
35
38
|
|
|
36
39
|
const COLOR_SCHEME_OPTIONS: ThemeOption[] = [
|