@voxgig/build 4.4.0 → 4.12.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/README.md +51 -2
- package/build.ts +20 -0
- package/dist/api/api_gen.d.ts +7 -0
- package/dist/api/api_gen.js +370 -0
- package/dist/api/api_gen.js.map +1 -0
- package/dist/build.d.ts +16 -1
- package/dist/build.js +17 -1
- package/dist/build.js.map +1 -1
- package/dist/doc/doc_gen.d.ts +8 -0
- package/dist/doc/doc_gen.js +397 -0
- package/dist/doc/doc_gen.js.map +1 -0
- package/dist/env/web/web_gen.js +197 -11
- package/dist/env/web/web_gen.js.map +1 -1
- package/dist/shape/ent.js +4 -2
- package/dist/shape/ent.js.map +1 -1
- package/env/web/web_gen.ts +219 -13
- package/package.json +6 -9
- package/tm/web/backend/env/shared/seed.ts.frag +23 -0
- package/tm/web/backend/env/web/api.ts.frag +157 -0
- package/tm/web/backend/env/web/web.ts.frag +43 -8
- package/tm/web/backend/srv/api/api-srv.ts.frag +4 -0
- package/tm/web/backend/srv/api/expose.ts.frag +33 -0
- package/tm/web/backend/srv/api/get_info.ts.frag +6 -0
- package/tm/web/backend/srv/api/on_ent.ts.frag +124 -0
- package/tm/web/backend/srv/auth/apikey_util.ts.frag +10 -0
- package/tm/web/backend/srv/auth/auth-srv.ts.frag +1 -0
- package/tm/web/backend/srv/auth/change_pass.ts.frag +12 -0
- package/tm/web/backend/srv/auth/create_apikey.ts.frag +33 -0
- package/tm/web/backend/srv/auth/get_info.ts.frag +1 -0
- package/tm/web/backend/srv/auth/list_apikey.ts.frag +15 -0
- package/tm/web/backend/srv/auth/load_auth.ts.frag +10 -1
- package/tm/web/backend/srv/auth/remind_pass.ts.frag +35 -0
- package/tm/web/backend/srv/auth/revoke_apikey.ts.frag +23 -0
- package/tm/web/backend/srv/auth/signin_user.ts.frag +12 -2
- package/tm/web/backend/srv/auth/signout_user.ts.frag +11 -1
- package/tm/web/backend/srv/auth/update_user.ts.frag +15 -0
- package/tm/web/backend/srv/auth/user_util.ts.frag +21 -0
- package/tm/web/backend/srv/auth/web_change_pass.ts.frag +16 -0
- package/tm/web/backend/srv/auth/web_create_apikey.ts.frag +15 -0
- package/tm/web/backend/srv/auth/web_list_apikey.ts.frag +11 -0
- package/tm/web/backend/srv/auth/web_load_auth.ts.frag +1 -0
- package/tm/web/backend/srv/auth/web_remind_pass.ts.frag +7 -0
- package/tm/web/backend/srv/auth/web_revoke_apikey.ts.frag +14 -0
- package/tm/web/backend/srv/auth/web_signin_user.ts.frag +1 -0
- package/tm/web/backend/srv/auth/web_signout_user.ts.frag +11 -3
- package/tm/web/backend/srv/auth/web_update_user.ts.frag +11 -0
- package/tm/web/backend/srv/ent/access.ts.frag +179 -0
- package/tm/web/backend/srv/ent/cmd_list.ts.frag +86 -0
- package/tm/web/backend/srv/ent/cmd_load.ts.frag +56 -0
- package/tm/web/backend/srv/ent/cmd_remove.ts.frag +60 -0
- package/tm/web/backend/srv/ent/cmd_save.ts.frag +141 -0
- package/tm/web/backend/srv/ent/ent-srv.ts.frag +4 -0
- package/tm/web/backend/srv/ent/get_info.ts.frag +6 -0
- package/tm/web/backend/srv/ent/web_cmd_list.ts.frag +17 -0
- package/tm/web/backend/srv/ent/web_cmd_load.ts.frag +17 -0
- package/tm/web/backend/srv/ent/web_cmd_remove.ts.frag +17 -0
- package/tm/web/backend/srv/ent/web_cmd_save.ts.frag +17 -0
- package/tm/web/backend/test/unit/env/web/api-router.test.ts.frag +315 -0
- package/tm/web/backend/test/unit/env/web/surface.test.ts.frag +109 -0
- package/tm/web/backend/test/unit/srv/api/api.setup.ts.frag +162 -0
- package/tm/web/backend/test/unit/srv/api/api.test.ts.frag +225 -0
- package/tm/web/backend/test/unit/srv/auth/apikey.test.ts.frag +152 -0
- package/tm/web/backend/test/unit/srv/auth/auth.setup.ts.frag +40 -0
- package/tm/web/backend/test/unit/srv/auth/session.test.ts.frag +189 -0
- package/tm/web/backend/test/unit/srv/ent/ent.setup.ts.frag +53 -0
- package/tm/web/backend/test/unit/srv/ent/proxy.test.ts.frag +109 -0
- package/tm/web/docs/explanation/web-architecture.md.frag +50 -0
- package/tm/web/docs/how-to/add-a-custom-entity-view.md.frag +53 -0
- package/tm/web/docs/how-to/change-the-theme.md.frag +63 -0
- package/tm/web/docs/how-to/customise-the-web-app.md.frag +68 -0
- package/tm/web/docs/how-to/use-the-api.md.frag +53 -0
- package/tm/web/docs/reference/web-app.md.frag +98 -0
- package/tm/web/web/AGENTS.md.frag +52 -0
- package/tm/web/web/e2e/smoke.spec.js.frag +13 -11
- package/tm/web/web/index.html.frag +1 -0
- package/tm/web/web/package.json.frag +1 -0
- package/tm/web/web/playwright.config.js.frag +2 -1
- package/tm/web/web/src/api.js.frag +92 -0
- package/tm/web/web/src/bus.js.frag +14 -8
- package/tm/web/web/src/cmp/admin.js.frag +293 -212
- package/tm/web/web/src/cmp/admin.md.frag +51 -0
- package/tm/web/web/src/cmp/app.js.frag +12 -15
- package/tm/web/web/src/cmp/app.md.frag +28 -0
- package/tm/web/web/src/cmp/auth.js.frag +69 -41
- package/tm/web/web/src/cmp/auth.md.frag +36 -0
- package/tm/web/web/src/cmp/public.js.frag +58 -0
- package/tm/web/web/src/cmp/public.md.frag +26 -0
- package/tm/web/web/src/cmp/settings.js.frag +178 -0
- package/tm/web/web/src/cmp/settings.md.frag +30 -0
- package/tm/web/web/src/cmp/shell.js.frag +231 -0
- package/tm/web/web/src/cmp/shell.md.frag +49 -0
- package/tm/web/web/src/cmp/view/custom-view.js.frag +47 -0
- package/tm/web/web/src/cmp/view/custom-view.md.frag +31 -0
- package/tm/web/web/src/custom.css.frag +8 -0
- package/tm/web/web/src/customise.js.frag +30 -0
- package/tm/web/web/src/hooks.js.frag +79 -0
- package/tm/web/web/src/main.js.frag +17 -4
- package/tm/web/web/src/model.js.frag +162 -0
- package/tm/web/web/src/style.css.frag +135 -21
- package/tm/web/web/src/theme.js.frag +82 -0
|
@@ -1,26 +1,140 @@
|
|
|
1
|
+
/* Design tokens (--vg-*) come from the model theme via theme.css; each mode
|
|
2
|
+
sets them under :root[data-theme-mode]. Override in custom.css. */
|
|
1
3
|
* { box-sizing: border-box; }
|
|
2
|
-
body { font:
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
.vg-
|
|
9
|
-
.vg-
|
|
10
|
-
.vg-
|
|
11
|
-
|
|
4
|
+
body { font: var(--vg-font); margin: 0; color: var(--vg-text); background: var(--vg-bg); }
|
|
5
|
+
button { cursor: pointer; }
|
|
6
|
+
a { color: var(--vg-primary); }
|
|
7
|
+
.vg-muted { color: var(--vg-muted); }
|
|
8
|
+
.vg-hint { color: var(--vg-muted); }
|
|
9
|
+
.vg-link { background: none; border: none; color: #1f6feb; cursor: pointer; padding: 0; font: inherit; text-decoration: none; }
|
|
10
|
+
.vg-link:hover { text-decoration: underline; }
|
|
11
|
+
.vg-primary { background: var(--vg-primary); color: #fff; border: none; border-radius: 6px; padding: 0.4rem 0.9rem; }
|
|
12
|
+
.vg-primary:hover { background: var(--vg-primary-dark); }
|
|
13
|
+
|
|
14
|
+
/* ---- enterprise shell ---- */
|
|
15
|
+
.vg-shell { display: flex; flex-direction: column; height: 100vh; }
|
|
16
|
+
.vg-topbar { display: flex; align-items: center; gap: 0.9rem; padding: 0 1rem; height: 52px;
|
|
17
|
+
background: var(--vg-topbar-bg); color: var(--vg-topbar-fg); flex: 0 0 auto; }
|
|
18
|
+
.vg-topbar .vg-brand { font-weight: 700; font-size: 1.05rem; white-space: nowrap; }
|
|
19
|
+
.vg-icon-btn { background: none; border: none; color: #e6edf3; font-size: 1.2rem; }
|
|
20
|
+
.vg-spacer { flex: 1; }
|
|
21
|
+
.vg-project-picker { display: flex; align-items: center; gap: 0.4rem; color: #aeb9c5; }
|
|
22
|
+
.vg-project-picker select { background: #1b2a3a; color: #e6edf3; border: 1px solid #2b3d4f;
|
|
23
|
+
border-radius: 6px; padding: 0.25rem 0.5rem; }
|
|
24
|
+
.vg-usermenu { position: relative; }
|
|
25
|
+
.vg-user-btn { background: #1b2a3a; color: #e6edf3; border: 1px solid #2b3d4f; border-radius: 6px;
|
|
26
|
+
padding: 0.3rem 0.7rem; display: flex; gap: 0.4rem; align-items: center; }
|
|
27
|
+
.vg-user-dropdown { position: absolute; right: 0; top: 110%; background: var(--vg-surface); color: var(--vg-text);
|
|
28
|
+
border: 1px solid var(--vg-border); border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.15);
|
|
29
|
+
min-width: 190px; display: flex; flex-direction: column; z-index: 20; }
|
|
30
|
+
.vg-user-dropdown[hidden] { display: none; }
|
|
31
|
+
.vg-user-dropdown a { padding: 0.6rem 0.9rem; text-decoration: none; color: var(--vg-text); }
|
|
32
|
+
.vg-user-dropdown a:hover { background: var(--vg-bg); }
|
|
33
|
+
|
|
34
|
+
.vg-body { display: flex; flex: 1; min-height: 0; }
|
|
35
|
+
.vg-sidebar { flex: 0 0 220px; background: var(--vg-surface); border-right: 1px solid var(--vg-border); overflow-y: auto;
|
|
36
|
+
padding: 0.7rem; }
|
|
37
|
+
.vg-collapsed .vg-sidebar { display: none; }
|
|
38
|
+
.vg-ent-filter { width: 100%; padding: 0.35rem 0.55rem; border: 1px solid var(--vg-border); border-radius: 6px;
|
|
39
|
+
margin-bottom: 0.6rem; }
|
|
40
|
+
.vg-navgroup { margin-bottom: 0.8rem; }
|
|
41
|
+
.vg-navgroup-title { font-size: 0.72rem; text-transform: uppercase; letter-spacing: .05em;
|
|
42
|
+
color: #8b949e; padding: 0.2rem 0.4rem; }
|
|
43
|
+
.vg-navlink { display: block; padding: 0.35rem 0.6rem; border-radius: 6px; text-decoration: none;
|
|
44
|
+
color: var(--vg-text); }
|
|
45
|
+
.vg-navlink:hover { background: var(--vg-bg); }
|
|
46
|
+
.vg-navlink.vg-sel { background: var(--vg-accent-bg); color: var(--vg-primary); font-weight: 600; }
|
|
47
|
+
|
|
48
|
+
.vg-main { flex: 1; overflow-y: auto; padding: 1.4rem; }
|
|
49
|
+
.vg-entity { max-width: 60rem; }
|
|
50
|
+
.vg-entity-head { display: flex; align-items: center; gap: 0.9rem; margin-bottom: 1rem; }
|
|
51
|
+
.vg-entity-head h2, .vg-entity-head h3 { margin: 0; flex: 1; }
|
|
52
|
+
.vg-empty { color: var(--vg-muted); padding: 2rem; text-align: center; }
|
|
53
|
+
|
|
54
|
+
/* ---- tables ---- */
|
|
55
|
+
.vg-table { border-collapse: collapse; width: 100%; background: var(--vg-surface); border: 1px solid var(--vg-border);
|
|
56
|
+
border-radius: 8px; overflow: hidden; }
|
|
57
|
+
.vg-table th, .vg-table td { text-align: left; padding: 0.5rem 0.7rem; border-bottom: 1px solid var(--vg-border); }
|
|
58
|
+
.vg-table th { background: var(--vg-bg); font-size: 0.8rem; text-transform: uppercase; letter-spacing: .03em;
|
|
59
|
+
color: var(--vg-muted); }
|
|
60
|
+
.vg-actions { text-align: right; white-space: nowrap; }
|
|
61
|
+
.vg-actions button { margin-left: 0.3rem; padding: 0.2rem 0.55rem; border: 1px solid var(--vg-border);
|
|
62
|
+
background: var(--vg-surface); border-radius: 5px; }
|
|
63
|
+
.vg-actions button:hover { background: var(--vg-bg); }
|
|
64
|
+
.vg-ref { text-decoration: none; }
|
|
65
|
+
.vg-ref:hover { text-decoration: underline; }
|
|
12
66
|
|
|
13
|
-
|
|
14
|
-
.vg-
|
|
15
|
-
|
|
67
|
+
/* ---- detail ---- */
|
|
68
|
+
.vg-detail { border-collapse: collapse; width: 100%; max-width: 40rem; margin-bottom: 1.6rem;
|
|
69
|
+
background: var(--vg-surface); border: 1px solid var(--vg-border); border-radius: 8px; overflow: hidden; }
|
|
70
|
+
.vg-detail th { text-align: left; width: 30%; padding: 0.5rem 0.7rem; color: var(--vg-muted);
|
|
71
|
+
background: var(--vg-bg); border-bottom: 1px solid var(--vg-border); }
|
|
72
|
+
.vg-detail td { padding: 0.5rem 0.7rem; border-bottom: 1px solid var(--vg-border); }
|
|
73
|
+
.vg-children { margin: 1.4rem 0; }
|
|
16
74
|
|
|
17
|
-
|
|
18
|
-
.vg-
|
|
19
|
-
border
|
|
75
|
+
/* ---- forms ---- */
|
|
76
|
+
.vg-entity-form { display: grid; gap: 0.7rem; max-width: 28rem; background: var(--vg-surface); padding: 1.2rem;
|
|
77
|
+
border: 1px solid var(--vg-border); border-radius: 8px; }
|
|
78
|
+
.vg-entity-form label { display: grid; gap: 0.25rem; font-size: 0.9rem; color: var(--vg-text); }
|
|
79
|
+
.vg-entity-form input, .vg-entity-form select { padding: 0.4rem 0.55rem; border: 1px solid var(--vg-border);
|
|
80
|
+
border-radius: 6px; font: inherit; }
|
|
81
|
+
.vg-entity-form input[type=checkbox] { justify-self: start; }
|
|
82
|
+
.vg-form-actions { display: flex; gap: 0.8rem; align-items: center; margin-top: 0.4rem; }
|
|
83
|
+
.vg-form-err { color: #b3261e; min-height: 1.2em; }
|
|
84
|
+
.vg-form-msg { min-height: 1.2em; }
|
|
85
|
+
.vg-form-msg.vg-ok { color: #1a7f37; }
|
|
86
|
+
.vg-form-msg.vg-err { color: #b3261e; }
|
|
87
|
+
.vg-settings .vg-card { background: var(--vg-surface); border: 1px solid var(--vg-border); border-radius: 8px;
|
|
88
|
+
padding: 1.2rem; margin-bottom: 1.2rem; max-width: 32rem; }
|
|
89
|
+
.vg-settings .vg-card h3 { margin-top: 0; }
|
|
90
|
+
|
|
91
|
+
/* ---- custom views (card grid) ---- */
|
|
92
|
+
.vg-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 1rem; }
|
|
93
|
+
.vg-card { background: var(--vg-surface); border: 1px solid var(--vg-border); border-radius: 10px; padding: 1.1rem; }
|
|
94
|
+
.vg-card h3 { margin: 0 0 0.3rem; }
|
|
95
|
+
.vg-card > p { margin: 0.2rem 0; }
|
|
96
|
+
.vg-proj-card { display: flex; flex-direction: column; gap: 0.4rem; }
|
|
97
|
+
.vg-proj-stats { display: flex; justify-content: space-between; color: var(--vg-muted); font-size: 0.85rem; }
|
|
98
|
+
.vg-proj-bar { height: 6px; background: var(--vg-border); border-radius: 4px; overflow: hidden; }
|
|
99
|
+
.vg-proj-bar > div { height: 100%; background: #1f6feb; }
|
|
100
|
+
.vg-proj-card .vg-open { align-self: flex-start; margin-top: 0.3rem; border: 1px solid var(--vg-border);
|
|
101
|
+
background: var(--vg-surface); border-radius: 5px; padding: 0.25rem 0.7rem; }
|
|
102
|
+
|
|
103
|
+
/* ---- public site ---- */
|
|
104
|
+
.vg-public-nav { display: flex; align-items: center; gap: 1.5rem; padding: 0.9rem 2rem;
|
|
105
|
+
background: #0d1b2a; color: #e6edf3; }
|
|
106
|
+
.vg-public-nav .vg-brand { font-weight: 700; font-size: 1.1rem; }
|
|
107
|
+
.vg-public-nav nav { margin-left: auto; display: flex; gap: 1.2rem; }
|
|
108
|
+
.vg-public-nav a { color: #aeb9c5; text-decoration: none; }
|
|
109
|
+
.vg-hero { display: flex; flex-wrap: wrap; gap: 2rem; padding: 3.5rem 2rem; max-width: 68rem;
|
|
110
|
+
margin: 0 auto; align-items: center; }
|
|
111
|
+
.vg-hero-copy { flex: 1 1 340px; }
|
|
112
|
+
.vg-hero-copy h1 { font-size: 2.4rem; margin: 0 0 0.8rem; }
|
|
113
|
+
.vg-hero-copy p { font-size: 1.15rem; color: #44515f; }
|
|
114
|
+
.vg-hero-auth { flex: 0 0 320px; }
|
|
115
|
+
.vg-features, .vg-about { max-width: 68rem; margin: 0 auto; padding: 2.5rem 2rem; }
|
|
116
|
+
.vg-feature-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1.2rem; }
|
|
117
|
+
.vg-feature { background: var(--vg-surface); border: 1px solid var(--vg-border); border-radius: 10px; padding: 1.2rem; }
|
|
118
|
+
.vg-feature h3 { margin-top: 0; }
|
|
119
|
+
.vg-public-footer { padding: 2rem; text-align: center; color: #8b949e; border-top: 1px solid var(--vg-border); }
|
|
120
|
+
|
|
121
|
+
/* ---- auth form ---- */
|
|
122
|
+
.vg-auth-form { display: grid; gap: 0.6rem; background: var(--vg-surface); padding: 1.4rem; border-radius: 10px;
|
|
123
|
+
border: 1px solid var(--vg-border); box-shadow: 0 6px 24px rgba(13,27,42,.08); }
|
|
124
|
+
.vg-auth-form h2 { margin: 0 0 0.3rem; }
|
|
125
|
+
.vg-auth-form label { display: grid; gap: 0.2rem; }
|
|
126
|
+
.vg-auth-form input { padding: 0.45rem 0.55rem; border: 1px solid var(--vg-border); border-radius: 6px; font: inherit; }
|
|
127
|
+
.vg-auth-form button[type=submit] { background: #1f6feb; color: #fff; border: none; border-radius: 6px;
|
|
128
|
+
padding: 0.5rem; font-size: 1rem; }
|
|
129
|
+
.vg-auth-err { color: #b3261e; min-height: 1.2em; }
|
|
130
|
+
.vg-auth-msg { color: #1a7f37; min-height: 1.2em; }
|
|
20
131
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
132
|
+
/* API keys (Settings & security) */
|
|
133
|
+
.vg-apikey-once { background: var(--vg-accent-bg); border: 1px solid var(--vg-border);
|
|
134
|
+
border-radius: var(--vg-radius); padding: 10px 12px; margin: 10px 0; }
|
|
135
|
+
.vg-apikey-value { display: block; margin-top: 6px; word-break: break-all; user-select: all; }
|
|
136
|
+
.vg-apikey-table { margin-top: 10px; }
|
|
137
|
+
.vg-apikey-revoke { background: none; border: 1px solid var(--vg-border); border-radius: var(--vg-radius);
|
|
138
|
+
color: var(--vg-text); padding: 2px 10px; cursor: pointer; }
|
|
139
|
+
.vg-apikey-revoke:hover { border-color: #b3261e; color: #b3261e; }
|
|
24
140
|
|
|
25
|
-
button { cursor: pointer; padding: 0.25rem 0.7rem; }
|
|
26
|
-
.vg-hint { color: #5b6b7b; }
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Theme controller. The design theme and its named modes come from the model
|
|
2
|
+
// (main.theme); the generated theme.css defines each mode's CSS variables.
|
|
3
|
+
// This switches the active mode via the `data-theme-mode` attribute on <html>,
|
|
4
|
+
// persisting the choice in localStorage. Modes can be customised with the
|
|
5
|
+
// `theme:modes` hook.
|
|
6
|
+
|
|
7
|
+
import { loadModel } from './model.js'
|
|
8
|
+
import * as Hooks from './hooks.js'
|
|
9
|
+
|
|
10
|
+
const KEY = 'vg-theme-mode'
|
|
11
|
+
let _default = 'light'
|
|
12
|
+
let _modes = ['light']
|
|
13
|
+
|
|
14
|
+
function saved() {
|
|
15
|
+
try {
|
|
16
|
+
return localStorage.getItem(KEY)
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function apply(mode) {
|
|
24
|
+
if (_modes.indexOf(mode) < 0) {
|
|
25
|
+
mode = _default
|
|
26
|
+
}
|
|
27
|
+
document.documentElement.setAttribute('data-theme-mode', mode)
|
|
28
|
+
try {
|
|
29
|
+
localStorage.setItem(KEY, mode)
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
// ignore storage failures
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// The available modes, filterable by the theme:modes hook.
|
|
37
|
+
function modes() {
|
|
38
|
+
return Hooks.filter('theme:modes', _modes.slice(), {})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function current() {
|
|
42
|
+
return document.documentElement.getAttribute('data-theme-mode') || _default
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function setMode(mode) {
|
|
46
|
+
apply(mode)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Cycle to the next mode (used by the shell's mode toggle).
|
|
50
|
+
function nextMode() {
|
|
51
|
+
const ms = modes()
|
|
52
|
+
const i = ms.indexOf(current())
|
|
53
|
+
setMode(ms[(i + 1) % ms.length])
|
|
54
|
+
return current()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function init() {
|
|
58
|
+
const m = await loadModel()
|
|
59
|
+
const theme = (m && m.main && m.main.theme) || {}
|
|
60
|
+
_modes = Object.keys(theme.modes || { light: {} })
|
|
61
|
+
_default = theme.mode || _modes[0] || 'light'
|
|
62
|
+
apply(saved() || _default)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Apply the persisted mode synchronously on import (before the model loads)
|
|
66
|
+
// to avoid a flash of the default theme; init() then refines the mode list.
|
|
67
|
+
if ('undefined' !== typeof document) {
|
|
68
|
+
const s = saved()
|
|
69
|
+
if (s) {
|
|
70
|
+
document.documentElement.setAttribute('data-theme-mode', s)
|
|
71
|
+
}
|
|
72
|
+
init()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
init,
|
|
77
|
+
modes,
|
|
78
|
+
current,
|
|
79
|
+
setMode,
|
|
80
|
+
nextMode,
|
|
81
|
+
}
|
|
82
|
+
|